microsoft / microsoft/TypeScript

JSDoc `@import` causes `getCompletionEntryDetails` to crash

Đang mở
#62,027 1 bình luận 1 reaction 1 người được giao Được giao cho @Copilot Xem trên GitHub
Bug Domain: JSDoc Help Wanted
Ngôn ngữ chính
Go
Star
111k
Fork
14.3k
Merge trung bình
2 ngày 4 giờ
Pull request đã merge (30 ngày)
132

Mô tả

### 🔎 Search Terms

jsdoc

The supplied value [object Object] did not pass the test 'isTypeKeywordToken'

### 🕗 Version & Regression Information

At least since TypeScript 5.5.0.

Since `@import` has been introduced in TypeScript 5.5.0 it does not make sense to look further back than that.

### ⏯ Playground Link

_No response_

### 💻 Code

tl;dr

try to run `getCompletionsAtPosition` on this file `main.js`:

```js
/** @import * as t from '/types.ts' */

import { preventDefault } from "/other-file.js";

p
```

`jsconfig.json`

```json
{
"compilerOptions": {
"checkJs": true,
"strict": true,
"paths": {
"/*": ["./*"],
},
"allowImportingTsExtensions": true
},
"include": ["**/*.js", "**/*.ts"],
}
```

`other-file.js`

```js
export function preventDefault() {

}
```

`types.ts`

```ts
export {};
```

And it will crash.

Here is a ChatGPT generated reproduction that should be self-contained:

```ts
#!/usr/bin/env ts-node

import fs from "fs";
import os from "os";
import path from "path";
import * as ts from "typescript";

// -------------------------------------------------------------------------------------
// Default jsconfig.json content to embed
// -------------------------------------------------------------------------------------
const defaultJsconfig = {
compilerOptions: {
checkJs: true,
strict: true,
paths: {
"/*": ["./*"]
},
allowImportingTsExtensions: true
},
include: ["**/*.js", "**/*.ts"]
};

// -------------------------------------------------------------------------------------
// Create synthetic project in a temp folder
// -------------------------------------------------------------------------------------
function createTempProject(): string {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ts-crash-"));

// Write the embedded jsconfig.json
fs.writeFileSync(
path.join(tempDir, "jsconfig.json"),
JSON.stringify(defaultJsconfig, null, 2),
"utf8"
);

// Dummy supporting files
fs.writeFileSync(
path.join(tempDir, "types.ts"),
"export interface Foo { bar: string }\nexport const baz = 42;\n",
);
fs.writeFileSync(
path.join(tempDir, "other-file.js"),
"export function preventDefault() {}\n"
);

// main.js with your exact snippet (absolute imports!)
fs.writeFileSync(
path.join(tempDir, "main.js"),
`/** @import * as t from '/types.ts' */\n\nimport { preventDefault } from "/other-file.js";\n\np`
);

return tempDir;
}

// -------------------------------------------------------------------------------------
// Build a Language-Service that *pretends* absolute paths exist but empty
// -------------------------------------------------------------------------------------
function createLanguageService(root: string): import("typescript").LanguageService {
const files = new Map();

const addFile = (filePath: string) => {
const full = path.resolve(root, filePath);
files.set(full, { text: fs.readFileSync(full, "utf8"), version: 0 });
};
["main.js", "types.ts", "other-file.js"].forEach(addFile);

const host: import("typescript").LanguageServiceHost = {
getCompilationSettings: () => ({
allowJs: true,
checkJs: true,
...defaultJsconfig.compilerOptions,
target: ts.ScriptTarget.ES2022
}),
getScriptFileNames: () => Array.from(files.keys()),
getScriptVersion: (f) => `${files.get(f)?.version ?? 0}`,
getScriptSnapshot: (f) => {
const entry = files.get(f);
if (entry) return ts.ScriptSnapshot.fromString(entry.text);
// Pretend absolute‑path imports exist but are empty strings
if (f.startsWith(path.sep)) return ts.ScriptSnapshot.fromString("");
return undefined;
},
getCurrentDirectory: () => root,
getDefaultLibFileName: (o) => ts.getDefaultLibFilePath(o),
fileExists: (f) => files.has(f) || f.startsWith(path.sep),
readFile: (f) => files.get(f)?.text ?? "",
readDirectory: ts.sys.readDirectory,
useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames,
};

return ts.createLanguageService(host, ts.createDocumentRegistry());
}

// -------------------------------------------------------------------------------------
// Attempt the crash reproduction
// -------------------------------------------------------------------------------------
function main() {
console.log(`Using TypeScript ${ts.version}`);

const projectDir = createTempProject();
const ls = createLanguageService(projectDir);
const targetFile = path.join(projectDir, "main.js");
const src = fs.readFileSync(targetFile, "utf8");
const pos = src.lastIndexOf("p") + 1;

const completionInfo = ls.getCompletionsAtPosition(targetFile, pos, {
includeExternalModuleExports: true,
includeInsertTextCompletions: true,
});

if (!completionInfo) {
console.error("No completions returned – cannot proceed.");
return;
}

console.log(`Entries: ${completionInfo.entries.length}\n`);
console.log("Running two passes (with & without entry.data)…\n");

const run = (withData: boolean) => {
console.log(withData ? "— WITH data —" : "— data = undefined —");
completionInfo.entries.forEach((entry) => {
try {
const details = ls.getCompletionEntryDetails(
targetFile,
pos,
entry.name,
{},
entry.source,
{},
withData ? entry.data : undefined,
);
} catch (e) {
console.error(`✗ ${entry.name} → ${(e as Error).message}`);
console.error((e as Error).stack);
}
});
console.log();
};

run(true);
run(false);

console.log(`Temp project retained at: ${projectDir}`);
}

main();

```

### 🙁 Actual behavior

Crash!

Running two passes (with & without entry.data)…

— WITH data —
✗ t → Debug Failure. Invalid cast. The supplied value [object Object] did not pass the test 'isTypeKeywordToken'.
Error: Debug Failure. Invalid cast. The supplied value [object Object] did not pass the test 'isTypeKeywordToken'.
at cast (/Users/bart/src/sqlife/repro/node_modules/typescript/lib/typescript.js:3350:16)
at getTypeKeywordOfTypeOnlyImport (/Users/bart/src/sqlife/repro/node_modules/typescript/lib/typescript.js:139958:10)
at promoteImportClause (/Users/bart/src/sqlife/repro/node_modules/typescript/lib/typescript.js:158612:32)
at promoteFromTypeOnly (/Users/bart/src/sqlife/repro/node_modules/typescript/lib/typescript.js:158602:7)
at codeActionForFixWorker (/Users/bart/src/sqlife/repro/node_modules/typescript/lib/typescript.js:158558:35)
at /Users/bart/src/sqlife/repro/node_modules/typescript/lib/typescript.js:158501:13
at _ChangeTracker.with (/Users/bart/src/sqlife/repro/node_modules/typescript/lib/typescript.js:178070:5)
at codeActionForFix (/Users/bart/src/sqlife/repro/node_modules/typescript/lib/typescript.js:158500:60)
at Object.getPromoteTypeOnlyCompletionAction (/Users/bart/src/sqlife/repro/node_modules/typescript/lib/typescript.js:157905:43)
at getCompletionEntryCodeActionsAndSourceDisplay (/Users/bart/src/sqlife/repro/node_modules/typescript/lib/typescript.js:167744:44)

— data = undefined —
✗ t → Debug Failure. Invalid cast. The supplied value [object Object] did not pass the test 'isTypeKeywordToken'.
Error: Debug Failure. Invalid cast. The supplied value [object Object] did not pass the test 'isTypeKeywordToken'.
at cast (/Users/bart/src/sqlife/repro/node_modules/typescript/lib/typescript.js:3350:16)
at getTypeKeywordOfTypeOnlyImport (/Users/bart/src/sqlife/repro/node_modules/typescript/lib/typescript.js:139958:10)
at promoteImportClause (/Users/bart/src/sqlife/repro/node_modules/typescript/lib/typescript.js:158612:32)
at promoteFromTypeOnly (/Users/bart/src/sqlife/repro/node_modules/typescript/lib/typescript.js:158602:7)
at codeActionForFixWorker (/Users/bart/src/sqlife/repro/node_modules/typescript/lib/typescript.js:158558:35)
at /Users/bart/src/sqlife/repro/node_modules/typescript/lib/typescript.js:158501:13
at _ChangeTracker.with (/Users/bart/src/sqlife/repro/node_modules/typescript/lib/typescript.js:178070:5)
at codeActionForFix (/Users/bart/src/sqlife/repro/node_modules/typescript/lib/typescript.js:158500:60)
at Object.getPromoteTypeOnlyCompletionAction (/Users/bart/src/sqlife/repro/node_modules/typescript/lib/typescript.js:157905:43)
at getCompletionEntryCodeActionsAndSourceDisplay (/Users/bart/src/sqlife/repro/node_modules/typescript/lib/typescript.js:167744:44)

### 🙂 Expected behavior

No crash

### Additional information about the issue

_No response_

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.