diff --git a/src/services/completions.ts b/src/services/completions.ts index 11fbce92c6b41..f85179da4e9c8 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -2550,6 +2550,11 @@ export function getCompletionEntriesFromSymbols( continue; } + // When in a value location in a JS file, ignore symbols that definitely seem to be type-only + if (!isTypeOnlyLocation && isInJSFile(sourceFile) && symbolAppearsToBeTypeOnly(symbol)) { + continue; + } + const { name, needsConvertPropertyAccess } = info; const originalSortText = symbolToSortTextMap?.[getSymbolId(symbol)] ?? SortText.LocationPriority; const sortText = isDeprecated(symbol, typeChecker) ? SortText.Deprecated(originalSortText) : originalSortText; @@ -2665,6 +2670,10 @@ export function getCompletionEntriesFromSymbols( // expressions are value space (which includes the value namespaces) return !!(allFlags & SymbolFlags.Value); } + + function symbolAppearsToBeTypeOnly(symbol: Symbol): boolean { + return !(symbol.flags & SymbolFlags.Value) && (!isInJSFile(symbol.declarations?.[0]) || !!(symbol.flags & SymbolFlags.Type)); + } } function getLabelCompletionAtPosition(node: BreakOrContinueStatement): CompletionInfo | undefined { diff --git a/src/testRunner/unittests/tsserver/completions.ts b/src/testRunner/unittests/tsserver/completions.ts index 787d034e7aee3..0286425a877f4 100644 --- a/src/testRunner/unittests/tsserver/completions.ts +++ b/src/testRunner/unittests/tsserver/completions.ts @@ -105,6 +105,7 @@ import { const localReact: File = { path: `${localAtTypes}/react/index.d.ts`, content: `import * as PropTypes from 'prop-types'; +export class Component {} `, }; const localReactRouterDomPackage: File = { @@ -131,6 +132,8 @@ import { | string | ((props: any, context?: any) => any) | (new (props: any, context?: any) => any); + +export const PropTypes = {}; `, }; @@ -198,6 +201,10 @@ export interface BrowserRouterProps { includeInsertTextCompletions: true, }, }); - baselineTsserverLogs("completions", "works when files are included from two different drives of windows", session); + baselineTsserverLogs( + "completions", + "works when files are included from two different drives of windows", + session, + ); }); }); diff --git a/tests/baselines/reference/jsFileImportNoTypes.baseline b/tests/baselines/reference/jsFileImportNoTypes.baseline new file mode 100644 index 0000000000000..e63c60c0f504e --- /dev/null +++ b/tests/baselines/reference/jsFileImportNoTypes.baseline @@ -0,0 +1,265 @@ +// === Completions === +=== /a.js === +// import { } from './declarations.ts' +// ^ +// | ---------------------------------------------------------------------- +// | class DeclaredClass +// | const declaredVariable: number +// | class TestClass +// | class TestClassInterfaceMerged +// | interface TestClassInterfaceMerged +// | enum TestEnum +// | function testFunction(): void +// | namespace TestNamespaceWithValue +// | const testValue: {} +// | ---------------------------------------------------------------------- + +[ + { + "marker": { + "fileName": "/a.js", + "position": 9, + "name": "" + }, + "item": { + "flags": 0, + "isGlobalCompletion": false, + "isMemberCompletion": true, + "isNewIdentifierLocation": false, + "entries": [ + { + "name": "DeclaredClass", + "kind": "class", + "kindModifiers": "export,declare", + "sortText": "11", + "displayParts": [ + { + "text": "class", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "DeclaredClass", + "kind": "className" + } + ], + "documentation": [] + }, + { + "name": "declaredVariable", + "kind": "const", + "kindModifiers": "export,declare", + "sortText": "11", + "displayParts": [ + { + "text": "const", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "declaredVariable", + "kind": "localName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "number", + "kind": "keyword" + } + ], + "documentation": [] + }, + { + "name": "TestClass", + "kind": "class", + "kindModifiers": "export", + "sortText": "11", + "displayParts": [ + { + "text": "class", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "TestClass", + "kind": "className" + } + ], + "documentation": [] + }, + { + "name": "TestClassInterfaceMerged", + "kind": "class", + "kindModifiers": "export", + "sortText": "11", + "displayParts": [ + { + "text": "class", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "TestClassInterfaceMerged", + "kind": "className" + }, + { + "text": "\n", + "kind": "lineBreak" + }, + { + "text": "interface", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "TestClassInterfaceMerged", + "kind": "className" + } + ], + "documentation": [] + }, + { + "name": "TestEnum", + "kind": "enum", + "kindModifiers": "export", + "sortText": "11", + "displayParts": [ + { + "text": "enum", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "TestEnum", + "kind": "enumName" + } + ], + "documentation": [] + }, + { + "name": "testFunction", + "kind": "function", + "kindModifiers": "export", + "sortText": "11", + "displayParts": [ + { + "text": "function", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "testFunction", + "kind": "functionName" + }, + { + "text": "(", + "kind": "punctuation" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "void", + "kind": "keyword" + } + ], + "documentation": [] + }, + { + "name": "TestNamespaceWithValue", + "kind": "module", + "kindModifiers": "export", + "sortText": "11", + "displayParts": [ + { + "text": "namespace", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "TestNamespaceWithValue", + "kind": "moduleName" + } + ], + "documentation": [] + }, + { + "name": "testValue", + "kind": "const", + "kindModifiers": "export", + "sortText": "11", + "displayParts": [ + { + "text": "const", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "testValue", + "kind": "localName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "{", + "kind": "punctuation" + }, + { + "text": "}", + "kind": "punctuation" + } + ], + "documentation": [] + } + ] + } + } +] \ No newline at end of file diff --git a/tests/baselines/reference/tsserver/completions/works-when-files-are-included-from-two-different-drives-of-windows.js b/tests/baselines/reference/tsserver/completions/works-when-files-are-included-from-two-different-drives-of-windows.js index f85ffbbe91020..6eee3bc754f68 100644 --- a/tests/baselines/reference/tsserver/completions/works-when-files-are-included-from-two-different-drives-of-windows.js +++ b/tests/baselines/reference/tsserver/completions/works-when-files-are-included-from-two-different-drives-of-windows.js @@ -10,6 +10,7 @@ import { //// [e:/myproject/node_modules/@types/react/index.d.ts] import * as PropTypes from 'prop-types'; +export class Component {} //// [e:/myproject/node_modules/@types/prop-types/index.d.ts] @@ -17,6 +18,8 @@ export type ReactComponentLike = | string | ((props: any, context?: any) => any) | (new (props: any, context?: any) => any); + +export const PropTypes = {}; //// [c:/typescript/node_modules/@types/react-router-dom/index.d.ts] @@ -30,6 +33,7 @@ export interface BrowserRouterProps { //// [c:/typescript/node_modules/@types/react/index.d.ts] import * as PropTypes from 'prop-types'; +export class Component {} //// [e:/myproject/package.json] @@ -125,9 +129,9 @@ Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /dev/null/inferre Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) Info seq [hh:mm:ss:mss] Files (6) c:/a/lib/lib.d.ts Text-1 "/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }" - e:/myproject/node_modules/@types/prop-types/index.d.ts Text-1 "export type ReactComponentLike =\n | string\n | ((props: any, context?: any) => any)\n | (new (props: any, context?: any) => any);\n" - e:/myproject/node_modules/@types/react/index.d.ts Text-1 "import * as PropTypes from 'prop-types';\n" - c:/typescript/node_modules/@types/react/index.d.ts Text-1 "import * as PropTypes from 'prop-types';\n" + e:/myproject/node_modules/@types/prop-types/index.d.ts Text-1 "export type ReactComponentLike =\n | string\n | ((props: any, context?: any) => any)\n | (new (props: any, context?: any) => any);\n\nexport const PropTypes = {};\n" + e:/myproject/node_modules/@types/react/index.d.ts Text-1 "import * as PropTypes from 'prop-types';\nexport class Component {}\n" + c:/typescript/node_modules/@types/react/index.d.ts Text-1 "import * as PropTypes from 'prop-types';\nexport class Component {}\n" c:/typescript/node_modules/@types/react-router-dom/index.d.ts Text-1 "import * as React from 'react';\nexport interface BrowserRouterProps {\n basename?: string;\n getUserConfirmation?: ((message: string, callback: (ok: boolean) => void) => void);\n forceRefresh?: boolean;\n keyLength?: number;\n}" e:/myproject/src/app.js SVC-1-0 "import React from 'react';\nimport {\n BrowserRouter as Router,\n} from \"react-router-dom\";\n" @@ -353,8 +357,8 @@ Info seq [hh:mm:ss:mss] getCompletionData: Get previous token: * Info seq [hh:mm:ss:mss] getCompletionsAtPosition: isCompletionListBlocker: * Info seq [hh:mm:ss:mss] getExportInfoMap: cache miss or empty; calculating new results Info seq [hh:mm:ss:mss] getExportInfoMap: done in * ms -Info seq [hh:mm:ss:mss] collectAutoImports: resolved 0 module specifiers, plus 0 ambient and 0 from cache -Info seq [hh:mm:ss:mss] collectAutoImports: response is complete +Info seq [hh:mm:ss:mss] collectAutoImports: resolved 0 module specifiers, plus 0 ambient and 2 from cache +Info seq [hh:mm:ss:mss] collectAutoImports: response is incomplete Info seq [hh:mm:ss:mss] collectAutoImports: * Info seq [hh:mm:ss:mss] getCompletionData: Semantic work: * Info seq [hh:mm:ss:mss] getCompletionsAtPosition: getCompletionEntriesFromSymbols: * @@ -648,6 +652,19 @@ Info seq [hh:mm:ss:mss] response: "kindModifiers": "", "sortText": "15" }, + { + "name": "Component", + "kind": "class", + "kindModifiers": "export,declare", + "sortText": "16", + "hasAction": true, + "source": "e:/myproject/node_modules/@types/react/index", + "data": { + "exportName": "Component", + "exportMapKey": "9 * Component ", + "fileName": "e:/myproject/node_modules/@types/react/index.d.ts" + } + }, { "name": "BrowserRouter", "kind": "warning", diff --git a/tests/cases/fourslash/importStatementCompletions_js.ts b/tests/cases/fourslash/importStatementCompletions_js.ts index fe4e0babad584..2473041b9b2c5 100644 --- a/tests/cases/fourslash/importStatementCompletions_js.ts +++ b/tests/cases/fourslash/importStatementCompletions_js.ts @@ -10,7 +10,9 @@ // @allowSyntheticDefaultImports: true // @Filename: /node_modules/react/index.d.ts -//// declare namespace React {} +//// declare namespace React { +//// export class Component {} +//// } //// export = React; // @Filename: /test.js diff --git a/tests/cases/fourslash/importStatementCompletions_js2.ts b/tests/cases/fourslash/importStatementCompletions_js2.ts index 431543ac58a3b..9bc00dcf4d7d9 100644 --- a/tests/cases/fourslash/importStatementCompletions_js2.ts +++ b/tests/cases/fourslash/importStatementCompletions_js2.ts @@ -12,7 +12,9 @@ // @noEmit: true // @Filename: /node_modules/react/index.d.ts -//// declare namespace React {} +//// declare namespace React { +//// export class Component {} +//// } //// export = React; // @Filename: /test.js diff --git a/tests/cases/fourslash/javascriptModulesTypeImport.ts b/tests/cases/fourslash/javascriptModulesTypeImport.ts new file mode 100644 index 0000000000000..674971ab0ca21 --- /dev/null +++ b/tests/cases/fourslash/javascriptModulesTypeImport.ts @@ -0,0 +1,19 @@ +/// +// @allowJs: true + +// @Filename: types.js +//// /** +//// * @typedef {Object} Pet +//// * @prop {string} name +//// */ +//// module.exports = { a: 1 }; + +// @Filename: app.js +//// /** +//// * @param { import("./types")./**/ } p +//// */ +//// function walk(p) { +//// console.log(`Walking ${p.name}...`); +//// } + +verify.completions({ marker: "", includes: "Pet" }); diff --git a/tests/cases/fourslash/javascriptModulesTypeImportAsValue.ts b/tests/cases/fourslash/javascriptModulesTypeImportAsValue.ts new file mode 100644 index 0000000000000..f35230dbc17e0 --- /dev/null +++ b/tests/cases/fourslash/javascriptModulesTypeImportAsValue.ts @@ -0,0 +1,14 @@ +/// +// @allowJs: true + +// @Filename: types.js +//// /** +//// * @typedef {Object} Pet +//// * @prop {string} name +//// */ +//// module.exports = { a: 1 }; + +// @Filename: app.js +//// import { /**/ } from "./types" + +verify.completions({ marker: "", excludes: "Pet" }); diff --git a/tests/cases/fourslash/jsFileImportNoTypes.ts b/tests/cases/fourslash/jsFileImportNoTypes.ts new file mode 100644 index 0000000000000..26e946f8afa48 --- /dev/null +++ b/tests/cases/fourslash/jsFileImportNoTypes.ts @@ -0,0 +1,34 @@ +/// + +// @allowJs: true + +// @filename: /declarations.ts +//// export class TestClass {} +//// export const testValue = {}; +//// export enum TestEnum {} +//// export function testFunction() {} +//// export interface testInterface {} +//// export namespace TestNamespaceEmpty {} +//// export namespace TestNamespaceWithType { +//// export type testTypeInner = boolean; +//// } +//// export namespace TestNamespaceWithValue { +//// export const testValueInner = true; +//// } +//// export type testType = {}; +//// +//// export interface TestInterfaceMerged {} +//// export interface TestInterfaceMerged {} +//// +//// export interface TestClassInterfaceMerged {} +//// export class TestClassInterfaceMerged {} +//// +//// export declare const declaredVariable: number; +//// export declare class DeclaredClass {} +//// export declare interface DeclaredInterface {} +//// export declare type DeclaredType = {}; + +// @filename: /a.js +////import { /**/ } from './declarations.ts' + +verify.baselineCompletions();