From 993c503c844c9318114ef27f4a63d8f0f9d36736 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 1 Mar 2021 12:09:45 -0800 Subject: [PATCH 1/3] Add 'data' property to completion entry for better coordination between completions and completion details (#42890) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add 'data' property to completion entry for better cooperation between completions and completion details * Add doc comment * Update API baselines * Add server test * Test session’s Full result * Fix tests * stableSort to fix server fourslash test * Explicit verification of data parameter --- src/compiler/checker.ts | 1 + src/compiler/types.ts | 1 + src/harness/client.ts | 17 ++- src/harness/fourslashImpl.ts | 28 +++-- src/harness/fourslashInterfaceImpl.ts | 1 + src/harness/harnessLanguageService.ts | 4 +- src/server/protocol.ts | 7 ++ src/server/session.ts | 20 ++-- src/services/completions.ts | 100 ++++++++++++++---- src/services/services.ts | 4 +- src/services/shims.ts | 6 +- src/services/types.ts | 29 ++++- .../cancellableLanguageServiceOperations.ts | 2 +- .../unittests/tsserver/completions.ts | 3 +- .../tsserver/partialSemanticServer.ts | 3 +- .../reference/api/tsserverlibrary.d.ts | 36 ++++++- tests/baselines/reference/api/typescript.d.ts | 29 ++++- .../completionListForUnicodeEscapeName.ts | 6 +- ...mpletionsImport_defaultAndNamedConflict.ts | 52 +++++++++ ...ompletionsImport_exportEquals_anonymous.ts | 12 ++- tests/cases/fourslash/fourslash.ts | 8 ++ ...nsImport_defaultAndNamedConflict_server.ts | 48 +++++++++ 22 files changed, 348 insertions(+), 69 deletions(-) create mode 100644 tests/cases/fourslash/completionsImport_defaultAndNamedConflict.ts create mode 100644 tests/cases/fourslash/server/completionsImport_defaultAndNamedConflict_server.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0329502b92115..572b1bb51938e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -600,6 +600,7 @@ namespace ts { }, tryGetMemberInModuleExports: (name, symbol) => tryGetMemberInModuleExports(escapeLeadingUnderscores(name), symbol), tryGetMemberInModuleExportsAndProperties: (name, symbol) => tryGetMemberInModuleExportsAndProperties(escapeLeadingUnderscores(name), symbol), + tryFindAmbientModule: moduleName => tryFindAmbientModule(moduleName, /*withAugmentations*/ true), tryFindAmbientModuleWithoutAugmentations: moduleName => { // we deliberately exclude augmentations // since we are only interested in declarations of the module itself diff --git a/src/compiler/types.ts b/src/compiler/types.ts index fdeb01c5484b3..db5c8c9dba97d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4165,6 +4165,7 @@ namespace ts { /* @internal */ createSymbol(flags: SymbolFlags, name: __String): TransientSymbol; /* @internal */ createIndexInfo(type: Type, isReadonly: boolean, declaration?: SignatureDeclaration): IndexInfo; /* @internal */ isSymbolAccessible(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, shouldComputeAliasToMarkVisible: boolean): SymbolAccessibilityResult; + /* @internal */ tryFindAmbientModule(moduleName: string): Symbol | undefined; /* @internal */ tryFindAmbientModuleWithoutAugmentations(moduleName: string): Symbol | undefined; /* @internal */ getSymbolWalker(accept?: (symbol: Symbol) => boolean): SymbolWalker; diff --git a/src/harness/client.ts b/src/harness/client.ts index f4f703d9164e3..4acfca3e0eb91 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -205,9 +205,9 @@ namespace ts.server { isNewIdentifierLocation: false, entries: response.body!.map(entry => { // TODO: GH#18217 if (entry.replacementSpan !== undefined) { - const { name, kind, kindModifiers, sortText, replacementSpan, hasAction, source, isRecommended } = entry; + const { name, kind, kindModifiers, sortText, replacementSpan, hasAction, source, data, isRecommended } = entry; // TODO: GH#241 - const res: CompletionEntry = { name, kind, kindModifiers, sortText, replacementSpan: this.decodeSpan(replacementSpan, fileName), hasAction, source, isRecommended }; + const res: CompletionEntry = { name, kind, kindModifiers, sortText, replacementSpan: this.decodeSpan(replacementSpan, fileName), hasAction, source, data: data as any, isRecommended }; return res; } @@ -216,14 +216,13 @@ namespace ts.server { }; } - getCompletionEntryDetails(fileName: string, position: number, entryName: string, _options: FormatCodeOptions | FormatCodeSettings | undefined, source: string | undefined): CompletionEntryDetails { - const args: protocol.CompletionDetailsRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), entryNames: [{ name: entryName, source }] }; + getCompletionEntryDetails(fileName: string, position: number, entryName: string, _options: FormatCodeOptions | FormatCodeSettings | undefined, source: string | undefined, _preferences: UserPreferences | undefined, data: unknown): CompletionEntryDetails { + const args: protocol.CompletionDetailsRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), entryNames: [{ name: entryName, source, data }] }; - const request = this.processRequest(CommandNames.CompletionDetails, args); - const response = this.processResponse(request); - Debug.assert(response.body!.length === 1, "Unexpected length of completion details response body."); - const convertedCodeActions = map(response.body![0].codeActions, ({ description, changes }) => ({ description, changes: this.convertChanges(changes, fileName) })); - return { ...response.body![0], codeActions: convertedCodeActions }; + const request = this.processRequest(CommandNames.CompletionDetailsFull, args); + const response = this.processResponse(request); + Debug.assert(response.body.length === 1, "Unexpected length of completion details response body."); + return response.body[0]; } getCompletionEntrySymbol(_fileName: string, _position: number, _entryName: string): Symbol { diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 454240b7a726d..7c48ffbec689f 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -399,7 +399,7 @@ namespace FourSlash { } const memo = Utils.memoize( (_version: number, _active: string, _caret: number, _selectEnd: number, _marker: string, ...args: any[]) => (ls[key] as Function)(...args), - (...args) => args.join("|,|") + (...args) => args.map(a => a && typeof a === "object" ? JSON.stringify(a) : a).join("|,|") ); proxy[key] = (...args: any[]) => memo( target.languageServiceAdapterHost.getScriptInfo(target.activeFile.fileName)!.version, @@ -867,7 +867,7 @@ namespace FourSlash { nameToEntries.set(entry.name, [entry]); } else { - if (entries.some(e => e.source === entry.source)) { + if (entries.some(e => e.source === entry.source && this.deepEqual(e.data, entry.data))) { this.raiseError(`Duplicate completions for ${entry.name}`); } entries.push(entry); @@ -885,8 +885,8 @@ namespace FourSlash { const name = typeof include === "string" ? include : include.name; const found = nameToEntries.get(name); if (!found) throw this.raiseError(`Includes: completion '${name}' not found.`); - assert(found.length === 1, `Must use 'exact' for multiple completions with same name: '${name}'`); - this.verifyCompletionEntry(ts.first(found), include); + if (!found.length) throw this.raiseError(`Includes: no completions with name '${name}' remain unmatched.`); + this.verifyCompletionEntry(found.shift()!, include); } } if (options.excludes) { @@ -933,7 +933,7 @@ namespace FourSlash { assert.equal(actual.sortText, expected.sortText || ts.Completions.SortText.LocationPriority, this.messageAtLastKnownMarker(`Actual entry: ${JSON.stringify(actual)}`)); if (expected.text !== undefined) { - const actualDetails = ts.Debug.checkDefined(this.getCompletionEntryDetails(actual.name, actual.source), `No completion details available for name '${actual.name}' and source '${actual.source}'`); + const actualDetails = ts.Debug.checkDefined(this.getCompletionEntryDetails(actual.name, actual.source, actual.data), `No completion details available for name '${actual.name}' and source '${actual.source}'`); assert.equal(ts.displayPartsToString(actualDetails.displayParts), expected.text, "Expected 'text' property to match 'displayParts' string"); assert.equal(ts.displayPartsToString(actualDetails.documentation), expected.documentation || "", "Expected 'documentation' property to match 'documentation' display parts string"); // TODO: GH#23587 @@ -1254,6 +1254,16 @@ namespace FourSlash { } + private deepEqual(a: unknown, b: unknown) { + try { + this.assertObjectsEqual(a, b); + return true; + } + catch { + return false; + } + } + public verifyDisplayPartsOfReferencedSymbol(expected: ts.SymbolDisplayPart[]) { const referencedSymbols = this.findReferencesAtCaret()!; @@ -1281,11 +1291,11 @@ namespace FourSlash { return this.languageService.getCompletionsAtPosition(this.activeFile.fileName, this.currentCaretPosition, options); } - private getCompletionEntryDetails(entryName: string, source?: string, preferences?: ts.UserPreferences): ts.CompletionEntryDetails | undefined { + private getCompletionEntryDetails(entryName: string, source: string | undefined, data: ts.CompletionEntryData | undefined, preferences?: ts.UserPreferences): ts.CompletionEntryDetails | undefined { if (preferences) { this.configure(preferences); } - return this.languageService.getCompletionEntryDetails(this.activeFile.fileName, this.currentCaretPosition, entryName, this.formatCodeSettings, source, preferences); + return this.languageService.getCompletionEntryDetails(this.activeFile.fileName, this.currentCaretPosition, entryName, this.formatCodeSettings, source, preferences, data); } private getReferencesAtCaret() { @@ -2796,14 +2806,14 @@ namespace FourSlash { public applyCodeActionFromCompletion(markerName: string, options: FourSlashInterface.VerifyCompletionActionOptions) { this.goToMarker(markerName); - const details = this.getCompletionEntryDetails(options.name, options.source, options.preferences); + const details = this.getCompletionEntryDetails(options.name, options.source, options.data, options.preferences); if (!details) { const completions = this.getCompletionListAtCaret(options.preferences)?.entries; const matchingName = completions?.filter(e => e.name === options.name); const detailMessage = matchingName?.length ? `\n Found ${matchingName.length} with name '${options.name}' from source(s) ${matchingName.map(e => `'${e.source}'`).join(", ")}.` : ` (In fact, there were no completions with name '${options.name}' at all.)`; - return this.raiseError(`No completions were found for the given name, source, and preferences.` + detailMessage); + return this.raiseError(`No completions were found for the given name, source/data, and preferences.` + detailMessage); } const codeActions = details.codeActions; if (codeActions?.length !== 1) { diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index 09b52997b1285..a909dd1c08027 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -1701,6 +1701,7 @@ namespace FourSlashInterface { export interface VerifyCompletionActionOptions extends NewContentOptions { name: string; source?: string; + data?: ts.CompletionEntryData; description: string; preferences?: ts.UserPreferences; } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 1a056683f912c..17026bd5001b0 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -474,8 +474,8 @@ namespace Harness.LanguageService { getCompletionsAtPosition(fileName: string, position: number, preferences: ts.UserPreferences | undefined): ts.CompletionInfo { return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position, preferences)); } - getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: ts.FormatCodeOptions | undefined, source: string | undefined, preferences: ts.UserPreferences | undefined): ts.CompletionEntryDetails { - return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName, JSON.stringify(formatOptions), source, preferences)); + getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: ts.FormatCodeOptions | undefined, source: string | undefined, preferences: ts.UserPreferences | undefined, data: ts.CompletionEntryData | undefined): ts.CompletionEntryDetails { + return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName, JSON.stringify(formatOptions), source, preferences, data)); } getCompletionEntrySymbol(): ts.Symbol { throw new Error("getCompletionEntrySymbol not implemented across the shim layer."); diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 297e0705fa3f9..776f0ed60398f 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -2169,6 +2169,7 @@ namespace ts.server.protocol { export interface CompletionEntryIdentifier { name: string; source?: string; + data?: unknown; } /** @@ -2255,6 +2256,12 @@ namespace ts.server.protocol { * in the project package.json. */ isPackageJsonImport?: true; + /** + * A property to be sent back to TS Server in the CompletionDetailsRequest, along with `name`, + * that allows TS Server to look up the symbol represented by the completion item, disambiguating + * items with the same name. + */ + data?: unknown; } /** diff --git a/src/server/session.ts b/src/server/session.ts index dc0d4f42f5c79..aeb695bf1477b 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1808,14 +1808,14 @@ namespace ts.server { if (kind === protocol.CommandTypes.CompletionsFull) return completions; const prefix = args.prefix || ""; - const entries = mapDefined(completions.entries, entry => { + const entries = stableSort(mapDefined(completions.entries, entry => { if (completions.isMemberCompletion || startsWith(entry.name.toLowerCase(), prefix.toLowerCase())) { - const { name, kind, kindModifiers, sortText, insertText, replacementSpan, hasAction, source, isRecommended, isPackageJsonImport } = entry; + const { name, kind, kindModifiers, sortText, insertText, replacementSpan, hasAction, source, isRecommended, isPackageJsonImport, data } = entry; const convertedSpan = replacementSpan ? toProtocolTextSpan(replacementSpan, scriptInfo) : undefined; // Use `hasAction || undefined` to avoid serializing `false`. - return { name, kind, kindModifiers, sortText, insertText, replacementSpan: convertedSpan, hasAction: hasAction || undefined, source, isRecommended, isPackageJsonImport }; + return { name, kind, kindModifiers, sortText, insertText, replacementSpan: convertedSpan, hasAction: hasAction || undefined, source, isRecommended, isPackageJsonImport, data }; } - }).sort((a, b) => compareStringsCaseSensitiveUI(a.name, b.name)); + }), (a, b) => compareStringsCaseSensitiveUI(a.name, b.name)); if (kind === protocol.CommandTypes.Completions) { if (completions.metadata) (entries as WithMetadata).metadata = completions.metadata; @@ -1837,8 +1837,8 @@ namespace ts.server { const formattingOptions = project.projectService.getFormatCodeOptions(file); const result = mapDefined(args.entryNames, entryName => { - const { name, source } = typeof entryName === "string" ? { name: entryName, source: undefined } : entryName; - return project.getLanguageService().getCompletionEntryDetails(file, position, name, formattingOptions, source, this.getPreferences(file)); + const { name, source, data } = typeof entryName === "string" ? { name: entryName, source: undefined, data: undefined } : entryName; + return project.getLanguageService().getCompletionEntryDetails(file, position, name, formattingOptions, source, this.getPreferences(file), data ? cast(data, isCompletionEntryData) : undefined); }); return simplifiedResult ? result.map(details => ({ ...details, codeActions: map(details.codeActions, action => this.mapCodeAction(action)) })) @@ -3118,4 +3118,12 @@ namespace ts.server { isDefinition }; } + + function isCompletionEntryData(data: any): data is CompletionEntryData { + return data === undefined || data && typeof data === "object" + && typeof data.exportName === "string" + && (data.fileName === undefined || typeof data.fileName === "string") + && (data.ambientModuleName === undefined || typeof data.ambientModuleName === "string" + && (data.isPackageJsonImport === undefined || typeof data.isPackageJsonImport === "boolean")); + } } diff --git a/src/services/completions.ts b/src/services/completions.ts index ad135959391c3..7245f4734a754 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -48,6 +48,8 @@ namespace ts.Completions { moduleSymbol: Symbol; isDefaultExport: boolean; isFromPackageJson?: boolean; + exportName: string; + fileName?: string; } function originIsThisType(origin: SymbolOriginInfo): boolean { @@ -104,7 +106,6 @@ namespace ts.Completions { export interface AutoImportSuggestion { symbol: Symbol; symbolName: string; - skipFilter: boolean; origin: SymbolOriginInfoExport; } export interface ImportSuggestionsForFileCache { @@ -422,6 +423,7 @@ namespace ts.Completions { ): CompletionEntry | undefined { let insertText: string | undefined; let replacementSpan = getReplacementSpanForContextToken(contextToken); + let data: CompletionEntryData | undefined; const insertQuestionDot = origin && originIsNullableMember(origin); const useBraces = origin && originIsSymbolMember(origin) || needsConvertPropertyAccess; @@ -472,6 +474,15 @@ namespace ts.Completions { return undefined; } + if (originIsExport(origin)) { + data = { + exportName: origin.exportName, + fileName: origin.fileName, + ambientModuleName: origin.fileName ? undefined : stripQuotes(origin.moduleSymbol.name), + isPackageJsonImport: origin.isFromPackageJson ? true : undefined, + }; + } + // TODO(drosen): Right now we just permit *all* semantic meanings when calling // 'getSymbolKind' which is permissible given that it is backwards compatible; but // really we should consider passing the meaning for the node so that we don't report @@ -491,6 +502,7 @@ namespace ts.Completions { insertText, replacementSpan, isPackageJsonImport: originIsPackageJsonImport(origin) || undefined, + data, }; } @@ -669,6 +681,7 @@ namespace ts.Completions { export interface CompletionEntryIdentifier { name: string; source?: string; + data?: CompletionEntryData; } export function getCompletionEntryDetails( @@ -1485,25 +1498,35 @@ namespace ts.Completions { if (shouldOfferImportCompletions()) { const lowerCaseTokenText = previousToken && isIdentifier(previousToken) ? previousToken.text.toLowerCase() : ""; - const autoImportSuggestions = getSymbolsFromOtherSourceFileExports(program.getCompilerOptions().target!, host); - if (!detailsEntryId && importSuggestionsCache) { - importSuggestionsCache.set(sourceFile.fileName, autoImportSuggestions, host.getProjectVersion && host.getProjectVersion()); + if (detailsEntryId?.data) { + const autoImport = getAutoImportSymbolFromCompletionEntryData(detailsEntryId.data); + if (autoImport) { + const symbolId = getSymbolId(autoImport.symbol); + symbols.push(autoImport.symbol); + symbolToOriginInfoMap[symbolId] = autoImport.origin; + } } - autoImportSuggestions.forEach(({ symbol, symbolName, skipFilter, origin }) => { - if (detailsEntryId) { - if (detailsEntryId.source && stripQuotes(origin.moduleSymbol.name) !== detailsEntryId.source) { + else { + const autoImportSuggestions = getSymbolsFromOtherSourceFileExports(program.getCompilerOptions().target!, host); + if (!detailsEntryId && importSuggestionsCache) { + importSuggestionsCache.set(sourceFile.fileName, autoImportSuggestions, host.getProjectVersion && host.getProjectVersion()); + } + autoImportSuggestions.forEach(({ symbol, symbolName, origin }) => { + if (detailsEntryId) { + if (detailsEntryId.source && stripQuotes(origin.moduleSymbol.name) !== detailsEntryId.source) { + return; + } + } + else if (!stringContainsCharactersInOrder(symbolName.toLowerCase(), lowerCaseTokenText)) { return; } - } - else if (!skipFilter && !stringContainsCharactersInOrder(symbolName.toLowerCase(), lowerCaseTokenText)) { - return; - } - const symbolId = getSymbolId(symbol); - symbols.push(symbol); - symbolToOriginInfoMap[symbolId] = origin; - symbolToSortTextMap[symbolId] = SortText.AutoImportSuggestions; - }); + const symbolId = getSymbolId(symbol); + symbols.push(symbol); + symbolToOriginInfoMap[symbolId] = origin; + symbolToSortTextMap[symbolId] = SortText.AutoImportSuggestions; + }); + } } filterGlobalCompletion(symbols); } @@ -1673,7 +1696,7 @@ namespace ts.Completions { const seenResolvedModules = new Map(); const results = createMultiMap(); - codefix.forEachExternalModuleToImportFrom(program, host, sourceFile, !detailsEntryId, /*useAutoImportProvider*/ true, (moduleSymbol, _, program, isFromPackageJson) => { + codefix.forEachExternalModuleToImportFrom(program, host, sourceFile, !detailsEntryId, /*useAutoImportProvider*/ true, (moduleSymbol, file, program, isFromPackageJson) => { // Perf -- ignore other modules if this is a request for details if (detailsEntryId && detailsEntryId.source && stripQuotes(moduleSymbol.name) !== detailsEntryId.source) { return; @@ -1689,7 +1712,7 @@ namespace ts.Completions { // Don't add another completion for `export =` of a symbol that's already global. // So in `declare namespace foo {} declare module "foo" { export = foo; }`, there will just be the global completion for `foo`. if (resolvedModuleSymbol !== moduleSymbol && every(resolvedModuleSymbol.declarations, isNonGlobalDeclaration)) { - pushSymbol(resolvedModuleSymbol, moduleSymbol, isFromPackageJson, /*skipFilter*/ true); + pushSymbol(resolvedModuleSymbol, InternalSymbolName.ExportEquals, moduleSymbol, file, isFromPackageJson); } for (const symbol of typeChecker.getExportsAndPropertiesOfModule(moduleSymbol)) { @@ -1698,14 +1721,14 @@ namespace ts.Completions { continue; } - pushSymbol(symbol, moduleSymbol, isFromPackageJson, /*skipFilter*/ false); + pushSymbol(symbol, symbol.name, moduleSymbol, file, isFromPackageJson); } }); log(`getSymbolsFromOtherSourceFileExports: ${timestamp() - startTime}`); return flatten(arrayFrom(results.values())); - function pushSymbol(symbol: Symbol, moduleSymbol: Symbol, isFromPackageJson: boolean, skipFilter: boolean) { + function pushSymbol(symbol: Symbol, exportName: string, moduleSymbol: Symbol, file: SourceFile | undefined, isFromPackageJson: boolean) { const isDefaultExport = symbol.escapedName === InternalSymbolName.Default; const nonLocalSymbol = symbol; if (isDefaultExport) { @@ -1718,17 +1741,50 @@ namespace ts.Completions { const symbolName = getNameForExportedSymbol(symbol, target); const existingSuggestions = results.get(getSymbolId(original)); if (!some(existingSuggestions, s => s.symbolName === symbolName && moduleSymbolsAreDuplicateOrigins(moduleSymbol, s.origin.moduleSymbol))) { - const origin: SymbolOriginInfoExport = { kind: SymbolOriginInfoKind.Export, moduleSymbol, isDefaultExport, isFromPackageJson }; + const origin: SymbolOriginInfoExport = { + kind: SymbolOriginInfoKind.Export, + moduleSymbol, + isDefaultExport, + isFromPackageJson, + exportName, + fileName: file?.fileName + }; results.add(getSymbolId(original), { symbol, symbolName, origin, - skipFilter, }); } } } + function getAutoImportSymbolFromCompletionEntryData(data: CompletionEntryData): { symbol: Symbol, origin: SymbolOriginInfoExport } | undefined { + const containingProgram = data.isPackageJsonImport ? host.getPackageJsonAutoImportProvider!()! : program; + const checker = containingProgram.getTypeChecker(); + const moduleSymbol = + data.ambientModuleName ? checker.tryFindAmbientModule(data.ambientModuleName) : + data.fileName ? checker.getMergedSymbol(Debug.checkDefined(containingProgram.getSourceFile(data.fileName)).symbol) : + undefined; + + if (!moduleSymbol) return undefined; + let symbol = data.exportName === InternalSymbolName.ExportEquals + ? checker.resolveExternalModuleSymbol(moduleSymbol) + : checker.tryGetMemberInModuleExportsAndProperties(data.exportName, moduleSymbol); + if (!symbol) return undefined; + const isDefaultExport = data.exportName === InternalSymbolName.Default; + symbol = isDefaultExport && getLocalSymbolForExportDefault(symbol) || symbol; + return { + symbol, + origin: { + kind: SymbolOriginInfoKind.Export, + moduleSymbol, + isDefaultExport, + exportName: data.exportName, + fileName: data.fileName, + } + }; + } + /** * Determines whether a module symbol is redundant with another for purposes of offering * auto-import completions for exports of the same symbol. Exports of the same symbol diff --git a/src/services/services.ts b/src/services/services.ts index 1463d81dfdecc..884e890dccb20 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1558,14 +1558,14 @@ namespace ts { options.triggerCharacter); } - function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions: FormatCodeSettings | undefined, source: string | undefined, preferences: UserPreferences = emptyOptions): CompletionEntryDetails | undefined { + function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions: FormatCodeSettings | undefined, source: string | undefined, preferences: UserPreferences = emptyOptions, data?: CompletionEntryData): CompletionEntryDetails | undefined { synchronizeHostData(); return Completions.getCompletionEntryDetails( program, log, getValidSourceFile(fileName), position, - { name, source }, + { name, source, data }, host, (formattingOptions && formatting.getFormatContext(formattingOptions, host))!, // TODO: GH#18217 preferences, diff --git a/src/services/shims.ts b/src/services/shims.ts index e986820765ad9..65ac30dbaef01 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -150,7 +150,7 @@ namespace ts { getEncodedSemanticClassifications(fileName: string, start: number, length: number, format?: SemanticClassificationFormat): string; getCompletionsAtPosition(fileName: string, position: number, preferences: UserPreferences | undefined): string; - getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined, preferences: UserPreferences | undefined): string; + getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined, preferences: UserPreferences | undefined, data: CompletionEntryData | undefined): string; getQuickInfoAtPosition(fileName: string, position: number): string; @@ -962,12 +962,12 @@ namespace ts { } /** Get a string based representation of a completion list entry details */ - public getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined, preferences: UserPreferences | undefined) { + public getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined, preferences: UserPreferences | undefined, data: CompletionEntryData | undefined) { return this.forwardJSONCall( `getCompletionEntryDetails('${fileName}', ${position}, '${entryName}')`, () => { const localOptions: FormatCodeOptions = formatOptions === undefined ? undefined : JSON.parse(formatOptions); - return this.languageService.getCompletionEntryDetails(fileName, position, entryName, localOptions, source, preferences); + return this.languageService.getCompletionEntryDetails(fileName, position, entryName, localOptions, source, preferences, data); } ); } diff --git a/src/services/types.ts b/src/services/types.ts index 82563150ecb8e..c09d0d7d59da3 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -424,10 +424,11 @@ namespace ts { * * @param fileName The path to the file * @param position A zero based index of the character where you want the entries - * @param entryName The name from an existing completion which came from `getCompletionsAtPosition` + * @param entryName The `name` from an existing completion which came from `getCompletionsAtPosition` * @param formatOptions How should code samples in the completions be formatted, can be undefined for backwards compatibility - * @param source Source code for the current file, can be undefined for backwards compatibility + * @param source `source` property from the completion entry * @param preferences User settings, can be undefined for backwards compatibility + * @param data `data` property from the completion entry */ getCompletionEntryDetails( fileName: string, @@ -436,6 +437,7 @@ namespace ts { formatOptions: FormatCodeOptions | FormatCodeSettings | undefined, source: string | undefined, preferences: UserPreferences | undefined, + data: CompletionEntryData | undefined, ): CompletionEntryDetails | undefined; getCompletionEntrySymbol(fileName: string, position: number, name: string, source: string | undefined): Symbol | undefined; @@ -1136,6 +1138,20 @@ namespace ts { entries: CompletionEntry[]; } + export interface CompletionEntryData { + /** The file name declaring the export's module symbol, if it was an external module */ + fileName?: string; + /** The module name (with quotes stripped) of the export's module symbol, if it was an ambient module */ + ambientModuleName?: string; + /** True if the export was found in the package.json AutoImportProvider */ + isPackageJsonImport?: true; + /** + * The name of the property or export in the module's symbol table. Differs from the completion name + * in the case of InternalSymbolName.ExportEquals and InternalSymbolName.Default. + */ + exportName: string; + } + // see comments in protocol.ts export interface CompletionEntry { name: string; @@ -1154,6 +1170,15 @@ namespace ts { isRecommended?: true; isFromUncheckedFile?: true; isPackageJsonImport?: true; + /** + * A property to be sent back to TS Server in the CompletionDetailsRequest, along with `name`, + * that allows TS Server to look up the symbol represented by the completion item, disambiguating + * items with the same name. Currently only defined for auto-import completions, but the type is + * `unknown` in the protocol, so it can be changed as needed to support other kinds of completions. + * The presence of this property should generally not be used to assume that this completion entry + * is an auto-import. + */ + data?: CompletionEntryData; } export interface CompletionEntryDetails { diff --git a/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts b/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts index bd8ed2714acd5..26ad16d8ebcef 100644 --- a/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts +++ b/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts @@ -47,7 +47,7 @@ namespace ts { placeOpenBraceOnNewLineForControlBlocks: false, }; verifyOperationCancelledAfter(file, 1, service => // The LS doesn't do any top-level checks on the token for completion entry details, so the first check is within the checker - service.getCompletionEntryDetails("file.ts", file.lastIndexOf("f"), "foo", options, /*content*/ undefined, {})!, r => assert.exists(r.displayParts) + service.getCompletionEntryDetails("file.ts", file.lastIndexOf("f"), "foo", options, /*source*/ undefined, {}, /*data*/ undefined)!, r => assert.exists(r.displayParts) ); }); diff --git a/src/testRunner/unittests/tsserver/completions.ts b/src/testRunner/unittests/tsserver/completions.ts index 2117d8924aa0c..ccf4bc934611d 100644 --- a/src/testRunner/unittests/tsserver/completions.ts +++ b/src/testRunner/unittests/tsserver/completions.ts @@ -39,6 +39,7 @@ namespace ts.projectSystem { isPackageJsonImport: undefined, sortText: Completions.SortText.AutoImportSuggestions, source: "/a", + data: { exportName: "foo", fileName: "/a.ts", ambientModuleName: undefined, isPackageJsonImport: undefined } }; assert.deepEqual(response, { isGlobalCompletion: true, @@ -50,7 +51,7 @@ namespace ts.projectSystem { const detailsRequestArgs: protocol.CompletionDetailsRequestArgs = { ...requestLocation, - entryNames: [{ name: "foo", source: "/a" }], + entryNames: [{ name: "foo", source: "/a", data: { exportName: "foo", fileName: "/a.ts" } }], }; const detailsResponse = executeSessionRequest(session, protocol.CommandTypes.CompletionDetails, detailsRequestArgs); diff --git a/src/testRunner/unittests/tsserver/partialSemanticServer.ts b/src/testRunner/unittests/tsserver/partialSemanticServer.ts index 2c8b8ed14b068..5ef5cb6ccecc4 100644 --- a/src/testRunner/unittests/tsserver/partialSemanticServer.ts +++ b/src/testRunner/unittests/tsserver/partialSemanticServer.ts @@ -70,7 +70,8 @@ import { something } from "something"; isPackageJsonImport: undefined, isRecommended: undefined, replacementSpan: undefined, - source: undefined + source: undefined, + data: undefined, }; } }); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index e02e91bf766ae..062b2c787032c 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5528,12 +5528,13 @@ declare namespace ts { * * @param fileName The path to the file * @param position A zero based index of the character where you want the entries - * @param entryName The name from an existing completion which came from `getCompletionsAtPosition` + * @param entryName The `name` from an existing completion which came from `getCompletionsAtPosition` * @param formatOptions How should code samples in the completions be formatted, can be undefined for backwards compatibility - * @param source Source code for the current file, can be undefined for backwards compatibility + * @param source `source` property from the completion entry * @param preferences User settings, can be undefined for backwards compatibility + * @param data `data` property from the completion entry */ - getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: FormatCodeOptions | FormatCodeSettings | undefined, source: string | undefined, preferences: UserPreferences | undefined): CompletionEntryDetails | undefined; + getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: FormatCodeOptions | FormatCodeSettings | undefined, source: string | undefined, preferences: UserPreferences | undefined, data: CompletionEntryData | undefined): CompletionEntryDetails | undefined; getCompletionEntrySymbol(fileName: string, position: number, name: string, source: string | undefined): Symbol | undefined; /** * Gets semantic information about the identifier at a particular position in a @@ -6093,6 +6094,19 @@ declare namespace ts { isNewIdentifierLocation: boolean; entries: CompletionEntry[]; } + interface CompletionEntryData { + /** The file name declaring the export's module symbol, if it was an external module */ + fileName?: string; + /** The module name (with quotes stripped) of the export's module symbol, if it was an ambient module */ + ambientModuleName?: string; + /** True if the export was found in the package.json AutoImportProvider */ + isPackageJsonImport?: true; + /** + * The name of the property or export in the module's symbol table. Differs from the completion name + * in the case of InternalSymbolName.ExportEquals and InternalSymbolName.Default. + */ + exportName: string; + } interface CompletionEntry { name: string; kind: ScriptElementKind; @@ -6110,6 +6124,15 @@ declare namespace ts { isRecommended?: true; isFromUncheckedFile?: true; isPackageJsonImport?: true; + /** + * A property to be sent back to TS Server in the CompletionDetailsRequest, along with `name`, + * that allows TS Server to look up the symbol represented by the completion item, disambiguating + * items with the same name. Currently only defined for auto-import completions, but the type is + * `unknown` in the protocol, so it can be changed as needed to support other kinds of completions. + * The presence of this property should generally not be used to assume that this completion entry + * is an auto-import. + */ + data?: CompletionEntryData; } interface CompletionEntryDetails { name: string; @@ -8162,6 +8185,7 @@ declare namespace ts.server.protocol { interface CompletionEntryIdentifier { name: string; source?: string; + data?: unknown; } /** * Completion entry details request; value of command field is @@ -8244,6 +8268,12 @@ declare namespace ts.server.protocol { * in the project package.json. */ isPackageJsonImport?: true; + /** + * A property to be sent back to TS Server in the CompletionDetailsRequest, along with `name`, + * that allows TS Server to look up the symbol represented by the completion item, disambiguating + * items with the same name. + */ + data?: unknown; } /** * Additional completion entry details, available on demand diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 7b086a2531e64..b2f6ada552140 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5528,12 +5528,13 @@ declare namespace ts { * * @param fileName The path to the file * @param position A zero based index of the character where you want the entries - * @param entryName The name from an existing completion which came from `getCompletionsAtPosition` + * @param entryName The `name` from an existing completion which came from `getCompletionsAtPosition` * @param formatOptions How should code samples in the completions be formatted, can be undefined for backwards compatibility - * @param source Source code for the current file, can be undefined for backwards compatibility + * @param source `source` property from the completion entry * @param preferences User settings, can be undefined for backwards compatibility + * @param data `data` property from the completion entry */ - getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: FormatCodeOptions | FormatCodeSettings | undefined, source: string | undefined, preferences: UserPreferences | undefined): CompletionEntryDetails | undefined; + getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: FormatCodeOptions | FormatCodeSettings | undefined, source: string | undefined, preferences: UserPreferences | undefined, data: CompletionEntryData | undefined): CompletionEntryDetails | undefined; getCompletionEntrySymbol(fileName: string, position: number, name: string, source: string | undefined): Symbol | undefined; /** * Gets semantic information about the identifier at a particular position in a @@ -6093,6 +6094,19 @@ declare namespace ts { isNewIdentifierLocation: boolean; entries: CompletionEntry[]; } + interface CompletionEntryData { + /** The file name declaring the export's module symbol, if it was an external module */ + fileName?: string; + /** The module name (with quotes stripped) of the export's module symbol, if it was an ambient module */ + ambientModuleName?: string; + /** True if the export was found in the package.json AutoImportProvider */ + isPackageJsonImport?: true; + /** + * The name of the property or export in the module's symbol table. Differs from the completion name + * in the case of InternalSymbolName.ExportEquals and InternalSymbolName.Default. + */ + exportName: string; + } interface CompletionEntry { name: string; kind: ScriptElementKind; @@ -6110,6 +6124,15 @@ declare namespace ts { isRecommended?: true; isFromUncheckedFile?: true; isPackageJsonImport?: true; + /** + * A property to be sent back to TS Server in the CompletionDetailsRequest, along with `name`, + * that allows TS Server to look up the symbol represented by the completion item, disambiguating + * items with the same name. Currently only defined for auto-import completions, but the type is + * `unknown` in the protocol, so it can be changed as needed to support other kinds of completions. + * The presence of this property should generally not be used to assume that this completion entry + * is an auto-import. + */ + data?: CompletionEntryData; } interface CompletionEntryDetails { name: string; diff --git a/tests/cases/fourslash/completionListForUnicodeEscapeName.ts b/tests/cases/fourslash/completionListForUnicodeEscapeName.ts index 7c18e6c82a08c..47bb4bbd0bed0 100644 --- a/tests/cases/fourslash/completionListForUnicodeEscapeName.ts +++ b/tests/cases/fourslash/completionListForUnicodeEscapeName.ts @@ -6,7 +6,7 @@ /////*3*/ verify.completions( - { marker: "0", includes: ["B", "\u0042"] }, - { marker: "2", excludes: ["C", "\u0043", "A", "\u0041"], isNewIdentifierLocation: true }, - { marker: "3", includes: ["B", "\u0042", "A", "\u0041", "C", "\u0043"] }, + { marker: "0", includes: ["B"] }, + { marker: "2", excludes: ["C", "A"], isNewIdentifierLocation: true }, + { marker: "3", includes: ["B", "A", "C"] }, ); diff --git a/tests/cases/fourslash/completionsImport_defaultAndNamedConflict.ts b/tests/cases/fourslash/completionsImport_defaultAndNamedConflict.ts new file mode 100644 index 0000000000000..5d8b8ba3cf363 --- /dev/null +++ b/tests/cases/fourslash/completionsImport_defaultAndNamedConflict.ts @@ -0,0 +1,52 @@ +/// + +// @noLib: true + +// @Filename: /someModule.ts +//// export const someModule = 0; +//// export default 1; + +// @Filename: /index.ts +//// someMo/**/ + +verify.completions({ + marker: "", + exact: [ + completion.globalThisEntry, + completion.undefinedVarEntry, + { + name: "someModule", + source: "/someModule", + sourceDisplay: "./someModule", + text: "const someModule: 0", + kind: "const", + kindModifiers: "export", + hasAction: true, + sortText: completion.SortText.AutoImportSuggestions + }, + { + name: "someModule", + source: "/someModule", + sourceDisplay: "./someModule", + text: "(property) default: 1", + kind: "property", + kindModifiers: "export", + hasAction: true, + sortText: completion.SortText.AutoImportSuggestions + }, + ...completion.statementKeywordsWithTypes + ], + preferences: { + includeCompletionsForModuleExports: true + } +}); + +verify.applyCodeActionFromCompletion("", { + name: "someModule", + source: "/someModule", + data: { exportName: "default", fileName: "/someModule.ts" }, + description: `Import default 'someModule' from module "./someModule"`, + newFileContent: `import someModule from "./someModule"; + +someMo` +}); diff --git a/tests/cases/fourslash/completionsImport_exportEquals_anonymous.ts b/tests/cases/fourslash/completionsImport_exportEquals_anonymous.ts index 6c80b652a9377..f279bef27cdbc 100644 --- a/tests/cases/fourslash/completionsImport_exportEquals_anonymous.ts +++ b/tests/cases/fourslash/completionsImport_exportEquals_anonymous.ts @@ -28,12 +28,20 @@ verify.completions( exact: [ completion.globalThisEntry, completion.undefinedVarEntry, - exportEntry, ...completion.statementKeywordsWithTypes ], preferences }, - { marker: "1", includes: exportEntry, preferences } + { + marker: "1", + exact: [ + completion.globalThisEntry, + completion.undefinedVarEntry, + exportEntry, + ...completion.statementKeywordsWithTypes + ], + preferences + } ); verify.applyCodeActionFromCompletion("0", { name: "fooBar", diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 387d843190454..bf81a295f023f 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -98,6 +98,13 @@ declare module ts { character: number; } + interface CompletionEntryData { + fileName?: string; + ambientModuleName?: string; + isPackageJsonImport?: true; + exportName: string; + } + function flatMap(array: ReadonlyArray, mapfn: (x: T, i: number) => U | ReadonlyArray | undefined): U[]; } @@ -253,6 +260,7 @@ declare namespace FourSlashInterface { applyCodeActionFromCompletion(markerName: string, options: { name: string, source?: string, + data?: ts.CompletionEntryData, description: string, newFileContent?: string, newRangeContent?: string, diff --git a/tests/cases/fourslash/server/completionsImport_defaultAndNamedConflict_server.ts b/tests/cases/fourslash/server/completionsImport_defaultAndNamedConflict_server.ts new file mode 100644 index 0000000000000..16dd9d7fb1dce --- /dev/null +++ b/tests/cases/fourslash/server/completionsImport_defaultAndNamedConflict_server.ts @@ -0,0 +1,48 @@ +/// + +// @Filename: /tsconfig.json +//// { "compilerOptions": { "noLib": true } } + +// @Filename: /someModule.ts +//// export const someModule = 0; +//// export default 1; + +// @Filename: /index.ts +//// someMo/**/ + +verify.completions({ + marker: "", + includes: [ + { + name: "someModule", + source: "/someModule", + sourceDisplay: "./someModule", + text: "const someModule: 0", + kind: "const", + kindModifiers: "export", + hasAction: true, + sortText: completion.SortText.AutoImportSuggestions + }, + { + name: "someModule", + source: "/someModule", + sourceDisplay: "./someModule", + text: "(property) default: 1", + kind: "property", + kindModifiers: "export", + hasAction: true, + sortText: completion.SortText.AutoImportSuggestions + }, + ], + preferences: { + includeCompletionsForModuleExports: true + } +}); + +verify.applyCodeActionFromCompletion("", { + name: "someModule", + source: "/someModule", + data: { exportName: "default", fileName: "/someModule.ts" }, + description: `Import default 'someModule' from module "./someModule"`, + newFileContent: `import someModule from "./someModule";\r\n\r\nsomeMo` +}); From 2a49cf7d3cd276e4b2311c757392ace136fe4ec0 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 1 Mar 2021 13:46:45 -0800 Subject: [PATCH 2/3] Update package-lock.json. (#43028) --- package-lock.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4d8d44169549b..151a41ff76ae1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -345,12 +345,12 @@ "dev": true }, "@octokit/plugin-rest-endpoint-methods": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.13.0.tgz", - "integrity": "sha512-Ofusy7BwHkU7z4TNsVdf7wm5W3KR625KqlQj4AiWPnBvclmZU0Y2bVK8b8Mz8nW7sEX9TJcCdX6KeaincE/cLw==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.13.1.tgz", + "integrity": "sha512-T9YhQqpbO9Onmg+FYk09uci9pfChg8CZR9GBaPJWj+bDSzictW1xnU0NtCSSKKyrwvpW/opu7CtuDSs/HF1Syg==", "dev": true, "requires": { - "@octokit/types": "^6.11.0", + "@octokit/types": "^6.11.1", "deprecation": "^2.3.1" } }, @@ -390,15 +390,15 @@ } }, "@octokit/rest": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.3.0.tgz", - "integrity": "sha512-R45oBVhnq3HAOGVtC6lHY7LX7TGWqbbcD4KvBHoT4QIjgJzfqKag3m/DUJwLnp8xrokz1spZmspTIXiDeQqJSA==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.3.1.tgz", + "integrity": "sha512-g57ebsk7dtbLjiPBgEYDAiDTsyQM9kvlIt0J5UN6OSjG82K6fQQck6HXPpwcyNIDqbN7lIaWr3nsz56jBfI6qg==", "dev": true, "requires": { "@octokit/core": "^3.2.3", "@octokit/plugin-paginate-rest": "^2.6.2", "@octokit/plugin-request-log": "^1.0.2", - "@octokit/plugin-rest-endpoint-methods": "4.13.0" + "@octokit/plugin-rest-endpoint-methods": "4.13.1" } }, "@octokit/types": { @@ -7887,9 +7887,9 @@ "dev": true }, "uglify-js": { - "version": "3.12.8", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.8.tgz", - "integrity": "sha512-fvBeuXOsvqjecUtF/l1dwsrrf5y2BCUk9AOJGzGcm6tE7vegku5u/YvqjyDaAGr422PLoLnrxg3EnRvTqsdC1w==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.0.tgz", + "integrity": "sha512-TWYSWa9T2pPN4DIJYbU9oAjQx+5qdV5RUDxwARg8fmJZrD/V27Zj0JngW5xg1DFz42G0uDYl2XhzF6alSzD62w==", "dev": true, "optional": true }, From aa67b16e996124ef55848eac58d7ee0b30d5b113 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 1 Mar 2021 14:32:28 -0800 Subject: [PATCH 3/3] Add undefined to Symbol.declarations' type (#42975) * Add undefined to Symbol.declarations' type Symbol.declarations now has type `Declaration[] | undefined`. I made a mistake somewhere in the checker related to JS checking, so there are quite a few test failures right now. * undo clever change to getDeclaringConstructor * Address PR comments 1. More early-returns. 2. More line breaks. --- src/compiler/binder.ts | 2 +- src/compiler/builderState.ts | 5 +- src/compiler/checker.ts | 543 ++++++++++-------- src/compiler/core.ts | 2 +- src/compiler/moduleSpecifiers.ts | 2 +- src/compiler/transformers/declarations.ts | 14 +- src/compiler/types.ts | 2 +- src/compiler/utilities.ts | 8 +- src/harness/fourslashImpl.ts | 4 +- src/services/callHierarchy.ts | 2 +- .../codefixes/convertFunctionToEs6Class.ts | 2 +- src/services/codefixes/generateAccessors.ts | 2 +- src/services/completions.ts | 4 +- src/services/findAllReferences.ts | 4 +- src/services/getEditsForFileRename.ts | 4 +- src/services/goToDefinition.ts | 6 +- src/services/importTracker.ts | 10 +- .../convertOverloadListToSingleSignature.ts | 4 +- .../convertParamsToDestructuredObject.ts | 2 +- src/services/refactors/extractType.ts | 2 +- src/services/refactors/moveToNewFile.ts | 3 + src/services/rename.ts | 2 +- src/services/symbolDisplay.ts | 40 +- .../reference/api/tsserverlibrary.d.ts | 2 +- tests/baselines/reference/api/typescript.d.ts | 2 +- 25 files changed, 364 insertions(+), 309 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index cae8da10305a0..056da9e347e64 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3266,7 +3266,7 @@ namespace ts { if (node.name) { setParent(node.name, node); } - file.bindDiagnostics.push(createDiagnosticForNode(symbolExport.declarations[0], Diagnostics.Duplicate_identifier_0, symbolName(prototypeSymbol))); + file.bindDiagnostics.push(createDiagnosticForNode(symbolExport.declarations![0], Diagnostics.Duplicate_identifier_0, symbolName(prototypeSymbol))); } symbol.exports!.set(prototypeSymbol.escapedName, prototypeSymbol); prototypeSymbol.parent = symbol; diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index 72c5c37b23382..a2c7741f0e8b0 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -166,7 +166,7 @@ namespace ts { // From ambient modules for (const ambientModule of program.getTypeChecker().getAmbientModules()) { - if (ambientModule.declarations.length > 1) { + if (ambientModule.declarations && ambientModule.declarations.length > 1) { addReferenceFromAmbientModule(ambientModule); } } @@ -174,6 +174,9 @@ namespace ts { return referencedFiles; function addReferenceFromAmbientModule(symbol: Symbol) { + if (!symbol.declarations) { + return; + } // Add any file other than our own as reference for (const declaration of symbol.declarations) { const declarationSourceFile = getSourceFileOfNode(declaration); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 572b1bb51938e..9d8edbe12a5fa 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1243,7 +1243,10 @@ namespace ts { // as we will already report a "Declaration name conflicts..." error, and this error // won't make much sense. if (target !== globalThisSymbol) { - error(getNameOfDeclaration(source.declarations[0]), Diagnostics.Cannot_augment_module_0_with_value_exports_because_it_resolves_to_a_non_module_entity, symbolToString(target)); + error( + source.declarations && getNameOfDeclaration(source.declarations[0]), + Diagnostics.Cannot_augment_module_0_with_value_exports_because_it_resolves_to_a_non_module_entity, + symbolToString(target)); } } else { // error @@ -1277,8 +1280,10 @@ namespace ts { return target; function addDuplicateLocations(locs: Declaration[], symbol: Symbol): void { - for (const decl of symbol.declarations) { - pushIfUnique(locs, decl); + if (symbol.declarations) { + for (const decl of symbol.declarations) { + pushIfUnique(locs, decl); + } } } } @@ -1321,11 +1326,11 @@ namespace ts { function mergeModuleAugmentation(moduleName: StringLiteral | Identifier): void { const moduleAugmentation = moduleName.parent; - if (moduleAugmentation.symbol.declarations[0] !== moduleAugmentation) { + if (moduleAugmentation.symbol.declarations?.[0] !== moduleAugmentation) { // this is a combined symbol for multiple augmentations within the same file. // its symbol already has accumulated information for all declarations // so we need to add it just once - do the work only for first declaration - Debug.assert(moduleAugmentation.symbol.declarations.length > 1); + Debug.assert(moduleAugmentation.symbol.declarations!.length > 1); return; } @@ -1824,7 +1829,7 @@ namespace ts { // ES6 exports are also visible locally (except for 'default'), but commonjs exports are not (except typedefs) if (name !== InternalSymbolName.Default && (result = lookup(moduleExports, name, meaning & SymbolFlags.ModuleMember))) { - if (isSourceFile(location) && location.commonJsModuleIndicator && !result.declarations.some(isJSDocTypeAlias)) { + if (isSourceFile(location) && location.commonJsModuleIndicator && !result.declarations?.some(isJSDocTypeAlias)) { result = undefined; } else { @@ -2204,11 +2209,13 @@ namespace ts { } function isTypeParameterSymbolDeclaredInContainer(symbol: Symbol, container: Node) { - for (const decl of symbol.declarations) { - if (decl.kind === SyntaxKind.TypeParameter) { - const parent = isJSDocTemplateTag(decl.parent) ? getJSDocHost(decl.parent) : decl.parent; - if (parent === container) { - return !(isJSDocTemplateTag(decl.parent) && find((decl.parent.parent as JSDoc).tags!, isJSDocTypeAlias)); // TODO: GH#18217 + if (symbol.declarations) { + for (const decl of symbol.declarations) { + if (decl.kind === SyntaxKind.TypeParameter) { + const parent = isJSDocTemplateTag(decl.parent) ? getJSDocHost(decl.parent) : decl.parent; + if (parent === container) { + return !(isJSDocTemplateTag(decl.parent) && find((decl.parent.parent as JSDoc).tags!, isJSDocTypeAlias)); // TODO: GH#18217 + } } } } @@ -2407,8 +2414,7 @@ namespace ts { return; } // Block-scoped variables cannot be used before their definition - const declaration = find( - result.declarations, + const declaration = result.declarations?.find( d => isBlockOrCatchScoped(d) || isClassLike(d) || (d.kind === SyntaxKind.EnumDeclaration)); if (declaration === undefined) return Debug.fail("checkResolvedBlockScopedVariable could not find block-scoped declaration"); @@ -2464,7 +2470,7 @@ namespace ts { } function getDeclarationOfAliasSymbol(symbol: Symbol): Declaration | undefined { - return find(symbol.declarations, isAliasSymbolDeclaration); + return symbol.declarations?.find(isAliasSymbolDeclaration); } /** @@ -2598,7 +2604,7 @@ namespace ts { exportDefaultSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, node, dontResolveAlias); } - const file = find(moduleSymbol.declarations, isSourceFile); + const file = moduleSymbol.declarations?.find(isSourceFile); const hasSyntheticDefault = canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias); if (!exportDefaultSymbol && !hasSyntheticDefault) { if (hasExportAssignmentSymbol(moduleSymbol)) { @@ -2641,7 +2647,7 @@ namespace ts { const diagnostic = error(node.name, Diagnostics.Module_0_has_no_default_export, symbolToString(moduleSymbol)); const exportStar = moduleSymbol.exports?.get(InternalSymbolName.ExportStar); if (exportStar) { - const defaultExport = find(exportStar.declarations, decl => !!( + const defaultExport = exportStar.declarations?.find(decl => !!( isExportDeclaration(decl) && decl.moduleSpecifier && resolveExternalModuleName(decl, decl.moduleSpecifier)?.exports?.has(InternalSymbolName.Default) )); @@ -2748,7 +2754,7 @@ namespace ts { let symbolFromModule = getExportOfModule(targetSymbol, name, specifier, dontResolveAlias); if (symbolFromModule === undefined && name.escapedText === InternalSymbolName.Default) { - const file = find(moduleSymbol.declarations, isSourceFile); + const file = moduleSymbol.declarations?.find(isSourceFile); if (canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias)) { symbolFromModule = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); } @@ -2802,10 +2808,11 @@ namespace ts { const exportedSymbol = exports ? find(symbolsToArray(exports), symbol => !!getSymbolIfSameReference(symbol, localSymbol)) : undefined; const diagnostic = exportedSymbol ? error(name, Diagnostics.Module_0_declares_1_locally_but_it_is_exported_as_2, moduleName, declarationName, symbolToString(exportedSymbol)) : error(name, Diagnostics.Module_0_declares_1_locally_but_it_is_not_exported, moduleName, declarationName); - - addRelatedInfo(diagnostic, - ...map(localSymbol.declarations, (decl, index) => - createDiagnosticForNode(decl, index === 0 ? Diagnostics._0_is_declared_here : Diagnostics.and_here, declarationName))); + if (localSymbol.declarations) { + addRelatedInfo(diagnostic, + ...map(localSymbol.declarations, (decl, index) => + createDiagnosticForNode(decl, index === 0 ? Diagnostics._0_is_declared_here : Diagnostics.and_here, declarationName))); + } } } else { @@ -3584,15 +3591,17 @@ namespace ts { if (exportStars) { const nestedSymbols = createSymbolTable(); const lookupTable: ExportCollisionTrackerTable = new Map(); - for (const node of exportStars.declarations) { - const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!); - const exportedSymbols = visit(resolvedModule); - extendExportSymbols( - nestedSymbols, - exportedSymbols, - lookupTable, - node as ExportDeclaration - ); + if (exportStars.declarations) { + for (const node of exportStars.declarations) { + const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!); + const exportedSymbols = visit(resolvedModule); + extendExportSymbols( + nestedSymbols, + exportedSymbols, + lookupTable, + node as ExportDeclaration + ); + } } lookupTable.forEach(({ exportsWithDuplicate }, id) => { // It's not an error if the file with multiple `export *`s with duplicate names exports a member with that name itself @@ -3731,7 +3740,7 @@ namespace ts { // If we're trying to reference some object literal in, eg `var a = { x: 1 }`, the symbol for the literal, `__object`, is distinct // from the symbol of the declaration it is being assigned to. Since we can use the declaration to refer to the literal, however, // we'd like to make that connection here - potentially causing us to paint the declaration's visibility, and therefore the literal. - const firstDecl: Node | false = !!length(symbol.declarations) && first(symbol.declarations); + const firstDecl: Node | false = !!length(symbol.declarations) && first(symbol.declarations!); if (meaning & SymbolFlags.Value && firstDecl && firstDecl.parent && isVariableDeclaration(firstDecl.parent)) { if (isObjectLiteralExpression(firstDecl) && firstDecl === firstDecl.parent.initializer || isTypeLiteralNode(firstDecl) && firstDecl === firstDecl.parent.type) { return getSymbolOfNode(firstDecl.parent); @@ -5116,8 +5125,8 @@ namespace ts { const saveEnclosingDeclaration = context.enclosingDeclaration; context.enclosingDeclaration = undefined; if (context.tracker.trackSymbol && getCheckFlags(propertySymbol) & CheckFlags.Late && isLateBoundName(propertySymbol.escapedName)) { - const decl = first(propertySymbol.declarations); - if (hasLateBindableName(decl)) { + const decl = first(propertySymbol.declarations!); + if (propertySymbol.declarations && hasLateBindableName(decl)) { if (isBinaryExpression(decl)) { const name = getNameOfDeclaration(decl); if (name && isElementAccessExpression(name) && isPropertyAccessEntityNameExpression(name.argumentExpression)) { @@ -5168,7 +5177,7 @@ namespace ts { function preserveCommentsOn(node: T) { if (some(propertySymbol.declarations, d => d.kind === SyntaxKind.JSDocPropertyTag)) { - const d = find(propertySymbol.declarations, d => d.kind === SyntaxKind.JSDocPropertyTag)! as JSDocPropertyTag; + const d = propertySymbol.declarations?.find(d => d.kind === SyntaxKind.JSDocPropertyTag)! as JSDocPropertyTag; const commentText = d.comment; if (commentText) { setSyntheticLeadingComments(node, [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "*\n * " + commentText.replace(/\n/g, "\n * ") + "\n ", pos: -1, end: -1, hasTrailingNewLine: true }]); @@ -5590,7 +5599,7 @@ namespace ts { if (context.tracker.trackReferencedAmbientModule) { const ambientDecls = filter(symbol.declarations, isAmbientModule); if (length(ambientDecls)) { - for (const decl of ambientDecls) { + for (const decl of ambientDecls!) { context.tracker.trackReferencedAmbientModule(decl, symbol); } } @@ -6516,7 +6525,7 @@ namespace ts { if (textRange && isVariableDeclarationList(textRange.parent) && textRange.parent.declarations.length === 1) { textRange = textRange.parent.parent; } - const propertyAccessRequire = find(symbol.declarations, isPropertyAccessExpression); + const propertyAccessRequire = symbol.declarations?.find(isPropertyAccessExpression); if (propertyAccessRequire && isBinaryExpression(propertyAccessRequire.parent) && isIdentifier(propertyAccessRequire.parent.right) && type.symbol && isSourceFile(type.symbol.valueDeclaration)) { const alias = localName === propertyAccessRequire.parent.right.escapedText ? undefined : propertyAccessRequire.parent.right; @@ -6604,10 +6613,12 @@ namespace ts { if (symbol.flags & SymbolFlags.ExportStar) { // synthesize export * from "moduleReference" // Straightforward - only one thing to do - make an export declaration - for (const node of symbol.declarations) { - const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!); - if (!resolvedModule) continue; - addResult(factory.createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, /*exportClause*/ undefined, factory.createStringLiteral(getSpecifierForModuleSymbol(resolvedModule, context))), ModifierFlags.None); + if (symbol.declarations) { + for (const node of symbol.declarations) { + const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!); + if (!resolvedModule) continue; + addResult(factory.createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, /*exportClause*/ undefined, factory.createStringLiteral(getSpecifierForModuleSymbol(resolvedModule, context))), ModifierFlags.None); + } } } if (needsPostExportDefault) { @@ -6678,7 +6689,7 @@ namespace ts { const aliasType = getDeclaredTypeOfTypeAlias(symbol); const typeParams = getSymbolLinks(symbol).typeParameters; const typeParamDecls = map(typeParams, p => typeParameterToDeclaration(p, context)); - const jsdocAliasDecl = find(symbol.declarations, isJSDocTypeAlias); + const jsdocAliasDecl = symbol.declarations?.find(isJSDocTypeAlias); const commentText = jsdocAliasDecl ? jsdocAliasDecl.comment || jsdocAliasDecl.parent.comment : undefined; const oldFlags = context.flags; context.flags |= NodeBuilderFlags.InTypeAlias; @@ -6903,7 +6914,7 @@ namespace ts { } function serializeAsClass(symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { - const originalDecl = find(symbol.declarations, isClassLike); + const originalDecl = symbol.declarations?.find(isClassLike); const oldEnclosing = context.enclosingDeclaration; context.enclosingDeclaration = originalDecl || oldEnclosing; const localParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); @@ -7326,7 +7337,7 @@ namespace ts { } const flag = (modifierFlags & ~ModifierFlags.Async) | (isStatic ? ModifierFlags.Static : 0); const name = getPropertyNameNodeForSymbol(p, context); - const firstPropertyLikeDecl = find(p.declarations, or(isPropertyDeclaration, isAccessor, isVariableDeclaration, isPropertySignature, isBinaryExpression, isPropertyAccessExpression)); + const firstPropertyLikeDecl = p.declarations?.find(or(isPropertyDeclaration, isAccessor, isVariableDeclaration, isPropertySignature, isBinaryExpression, isPropertyAccessExpression)); if (p.flags & SymbolFlags.Accessor && useAccessors) { const result: AccessorDeclaration[] = []; if (p.flags & SymbolFlags.SetAccessor) { @@ -7343,7 +7354,7 @@ namespace ts { isPrivate ? undefined : serializeTypeForDeclaration(context, getTypeOfSymbol(p), p, enclosingDeclaration, includePrivateSymbol, bundled) )], /*body*/ undefined - ), find(p.declarations, isSetAccessor) || firstPropertyLikeDecl)); + ), p.declarations?.find(isSetAccessor) || firstPropertyLikeDecl)); } if (p.flags & SymbolFlags.GetAccessor) { const isPrivate = modifierFlags & ModifierFlags.Private; @@ -7354,7 +7365,7 @@ namespace ts { [], isPrivate ? undefined : serializeTypeForDeclaration(context, getTypeOfSymbol(p), p, enclosingDeclaration, includePrivateSymbol, bundled), /*body*/ undefined - ), find(p.declarations, isGetAccessor) || firstPropertyLikeDecl)); + ), p.declarations?.find(isGetAccessor) || firstPropertyLikeDecl)); } return result; } @@ -7370,7 +7381,7 @@ namespace ts { // TODO: https://github.com/microsoft/TypeScript/pull/32372#discussion_r328386357 // interface members can't have initializers, however class members _can_ /*initializer*/ undefined - ), find(p.declarations, or(isPropertyDeclaration, isVariableDeclaration)) || firstPropertyLikeDecl); + ), p.declarations?.find(or(isPropertyDeclaration, isVariableDeclaration)) || firstPropertyLikeDecl); } if (p.flags & (SymbolFlags.Method | SymbolFlags.Function)) { const type = getTypeOfSymbol(p); @@ -7383,7 +7394,7 @@ namespace ts { p.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, /*type*/ undefined, /*initializer*/ undefined - ), find(p.declarations, isFunctionLikeDeclaration) || signatures[0] && signatures[0].declaration || p.declarations[0]); + ), p.declarations?.find(isFunctionLikeDeclaration) || signatures[0] && signatures[0].declaration || p.declarations && p.declarations[0]); } const results = []; @@ -7629,7 +7640,7 @@ namespace ts { } function getTypeAliasForTypeLiteral(type: Type): Symbol | undefined { - if (type.symbol && type.symbol.flags & SymbolFlags.TypeLiteral) { + if (type.symbol && type.symbol.flags & SymbolFlags.TypeLiteral && type.symbol.declarations) { const node = walkUpParenthesizedTypes(type.symbol.declarations[0].parent); if (node.kind === SyntaxKind.TypeAliasDeclaration) { return getSymbolOfNode(node); @@ -7864,7 +7875,7 @@ namespace ts { } return result; - function buildVisibleNodeList(declarations: Declaration[]) { + function buildVisibleNodeList(declarations: Declaration[] | undefined) { forEach(declarations, declaration => { const resultNode = getAnyImportSyntax(declaration) || declaration; if (setVisibility) { @@ -8354,12 +8365,15 @@ namespace ts { } function getDeclaringConstructor(symbol: Symbol) { + if (!symbol.declarations) { + return; + } for (const declaration of symbol.declarations) { const container = getThisContainer(declaration, /*includeArrowFunctions*/ false); if (container && (container.kind === SyntaxKind.Constructor || isJSConstructor(container))) { - return container; + return container as ConstructorDeclaration; } - } + }; } function getFlowTypeInConstructor(symbol: Symbol, constructor: ConstructorDeclaration) { @@ -8403,40 +8417,42 @@ namespace ts { type = getFlowTypeInConstructor(symbol, getDeclaringConstructor(symbol)!); } if (!type) { - let jsdocType: Type | undefined; let types: Type[] | undefined; - for (const declaration of symbol.declarations) { - const expression = (isBinaryExpression(declaration) || isCallExpression(declaration)) ? declaration : - isAccessExpression(declaration) ? isBinaryExpression(declaration.parent) ? declaration.parent : declaration : - undefined; - if (!expression) { - continue; // Non-assignment declaration merged in (eg, an Identifier to mark the thing as a namespace) - skip over it and pull type info from elsewhere - } + if (symbol.declarations) { + let jsdocType: Type | undefined; + for (const declaration of symbol.declarations) { + const expression = (isBinaryExpression(declaration) || isCallExpression(declaration)) ? declaration : + isAccessExpression(declaration) ? isBinaryExpression(declaration.parent) ? declaration.parent : declaration : + undefined; + if (!expression) { + continue; // Non-assignment declaration merged in (eg, an Identifier to mark the thing as a namespace) - skip over it and pull type info from elsewhere + } - const kind = isAccessExpression(expression) - ? getAssignmentDeclarationPropertyAccessKind(expression) - : getAssignmentDeclarationKind(expression); - if (kind === AssignmentDeclarationKind.ThisProperty || isBinaryExpression(expression) && isPossiblyAliasedThisProperty(expression, kind)) { - if (isDeclarationInConstructor(expression)) { - definedInConstructor = true; + const kind = isAccessExpression(expression) + ? getAssignmentDeclarationPropertyAccessKind(expression) + : getAssignmentDeclarationKind(expression); + if (kind === AssignmentDeclarationKind.ThisProperty || isBinaryExpression(expression) && isPossiblyAliasedThisProperty(expression, kind)) { + if (isDeclarationInConstructor(expression)) { + definedInConstructor = true; + } + else { + definedInMethod = true; + } } - else { - definedInMethod = true; + if (!isCallExpression(expression)) { + jsdocType = getAnnotatedTypeForAssignmentDeclaration(jsdocType, expression, symbol, declaration); + } + if (!jsdocType) { + (types || (types = [])).push((isBinaryExpression(expression) || isCallExpression(expression)) ? getInitializerTypeFromAssignmentDeclaration(symbol, resolvedSymbol, expression, kind) : neverType); } } - if (!isCallExpression(expression)) { - jsdocType = getAnnotatedTypeForAssignmentDeclaration(jsdocType, expression, symbol, declaration); - } - if (!jsdocType) { - (types || (types = [])).push((isBinaryExpression(expression) || isCallExpression(expression)) ? getInitializerTypeFromAssignmentDeclaration(symbol, resolvedSymbol, expression, kind) : neverType); - } + type = jsdocType; } - type = jsdocType; if (!type) { if (!length(types)) { return errorType; // No types from any declarations :( } - let constructorTypes = definedInConstructor ? getConstructorDefinedThisAssignmentTypes(types!, symbol.declarations) : undefined; + let constructorTypes = definedInConstructor && symbol.declarations ? getConstructorDefinedThisAssignmentTypes(types!, symbol.declarations) : undefined; // use only the constructor types unless they were only assigned null | undefined (including widening variants) if (definedInMethod) { const propType = getTypeOfPropertyInBaseClass(symbol); @@ -9274,6 +9290,9 @@ namespace ts { // The local type parameters are the combined set of type parameters from all declarations of the class, // interface, or type alias. function getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: Symbol): TypeParameter[] | undefined { + if (!symbol.declarations) { + return; + } let result: TypeParameter[] | undefined; for (const node of symbol.declarations) { if (node.kind === SyntaxKind.InterfaceDeclaration || @@ -9380,7 +9399,9 @@ namespace ts { ctorReturn = getReturnTypeOfSignature(ctorSig[0]); } } - addRelatedInfo(err, createDiagnosticForNode(baseConstructorType.symbol.declarations[0], Diagnostics.Did_you_mean_for_0_to_be_constrained_to_type_new_args_Colon_any_1, symbolToString(baseConstructorType.symbol), typeToString(ctorReturn))); + if (baseConstructorType.symbol.declarations) { + addRelatedInfo(err, createDiagnosticForNode(baseConstructorType.symbol.declarations[0], Diagnostics.Did_you_mean_for_0_to_be_constrained_to_type_new_args_Colon_any_1, symbolToString(baseConstructorType.symbol), typeToString(ctorReturn))); + } } return type.resolvedBaseConstructorType = errorType; } @@ -9391,17 +9412,19 @@ namespace ts { function getImplementsTypes(type: InterfaceType): BaseType[] { let resolvedImplementsTypes: BaseType[] = emptyArray; - for (const declaration of type.symbol.declarations) { - const implementsTypeNodes = getEffectiveImplementsTypeNodes(declaration as ClassLikeDeclaration); - if (!implementsTypeNodes) continue; - for (const node of implementsTypeNodes) { - const implementsType = getTypeFromTypeNode(node); - if (implementsType !== errorType) { - if (resolvedImplementsTypes === emptyArray) { - resolvedImplementsTypes = [implementsType]; - } - else { - resolvedImplementsTypes.push(implementsType); + if (type.symbol.declarations) { + for (const declaration of type.symbol.declarations) { + const implementsTypeNodes = getEffectiveImplementsTypeNodes(declaration as ClassLikeDeclaration); + if (!implementsTypeNodes) continue; + for (const node of implementsTypeNodes) { + const implementsType = getTypeFromTypeNode(node); + if (implementsType !== errorType) { + if (resolvedImplementsTypes === emptyArray) { + resolvedImplementsTypes = [implementsType]; + } + else { + resolvedImplementsTypes.push(implementsType); + } } } } @@ -9430,7 +9453,7 @@ namespace ts { else { Debug.fail("type must be class or interface"); } - if (!popTypeResolution()) { + if (!popTypeResolution() && type.symbol.declarations) { for (const declaration of type.symbol.declarations) { if (declaration.kind === SyntaxKind.ClassDeclaration || declaration.kind === SyntaxKind.InterfaceDeclaration) { reportCircularBaseType(declaration, type); @@ -9532,27 +9555,29 @@ namespace ts { function resolveBaseTypesOfInterface(type: InterfaceType): void { type.resolvedBaseTypes = type.resolvedBaseTypes || emptyArray; - for (const declaration of type.symbol.declarations) { - if (declaration.kind === SyntaxKind.InterfaceDeclaration && getInterfaceBaseTypeNodes(declaration)) { - for (const node of getInterfaceBaseTypeNodes(declaration)!) { - const baseType = getReducedType(getTypeFromTypeNode(node)); - if (baseType !== errorType) { - if (isValidBaseType(baseType)) { - if (type !== baseType && !hasBaseType(baseType, type)) { - if (type.resolvedBaseTypes === emptyArray) { - type.resolvedBaseTypes = [baseType]; + if (type.symbol.declarations) { + for (const declaration of type.symbol.declarations) { + if (declaration.kind === SyntaxKind.InterfaceDeclaration && getInterfaceBaseTypeNodes(declaration)) { + for (const node of getInterfaceBaseTypeNodes(declaration)!) { + const baseType = getReducedType(getTypeFromTypeNode(node)); + if (baseType !== errorType) { + if (isValidBaseType(baseType)) { + if (type !== baseType && !hasBaseType(baseType, type)) { + if (type.resolvedBaseTypes === emptyArray) { + type.resolvedBaseTypes = [baseType]; + } + else { + type.resolvedBaseTypes.push(baseType); + } } else { - type.resolvedBaseTypes.push(baseType); + reportCircularBaseType(declaration, type); } } else { - reportCircularBaseType(declaration, type); + error(node, Diagnostics.An_interface_can_only_extend_an_object_type_or_intersection_of_object_types_with_statically_known_members); } } - else { - error(node, Diagnostics.An_interface_can_only_extend_an_object_type_or_intersection_of_object_types_with_statically_known_members); - } } } } @@ -9567,6 +9592,9 @@ namespace ts { * and if none of the base interfaces have a "this" type. */ function isThislessInterface(symbol: Symbol): boolean { + if (!symbol.declarations) { + return true; + } for (const declaration of symbol.declarations) { if (declaration.kind === SyntaxKind.InterfaceDeclaration) { if (declaration.flags & NodeFlags.ContainsThis) { @@ -9633,7 +9661,7 @@ namespace ts { return errorType; } - const declaration = Debug.checkDefined(find(symbol.declarations, isTypeAlias), "Type alias symbol with no valid declaration found"); + const declaration = Debug.checkDefined(symbol.declarations?.find(isTypeAlias), "Type alias symbol with no valid declaration found"); const typeNode = isJSDocTypeAlias(declaration) ? declaration.typeExpression : declaration.type; // If typeNode is missing, we will error in checkJSDocTypedefTag. let type = typeNode ? getTypeFromTypeNode(typeNode) : errorType; @@ -9695,14 +9723,16 @@ namespace ts { return links.enumKind; } let hasNonLiteralMember = false; - for (const declaration of symbol.declarations) { - if (declaration.kind === SyntaxKind.EnumDeclaration) { - for (const member of (declaration).members) { - if (member.initializer && isStringLiteralLike(member.initializer)) { - return links.enumKind = EnumKind.Literal; - } - if (!isLiteralEnumMember(member)) { - hasNonLiteralMember = true; + if (symbol.declarations) { + for (const declaration of symbol.declarations) { + if (declaration.kind === SyntaxKind.EnumDeclaration) { + for (const member of (declaration).members) { + if (member.initializer && isStringLiteralLike(member.initializer)) { + return links.enumKind = EnumKind.Literal; + } + if (!isLiteralEnumMember(member)) { + hasNonLiteralMember = true; + } } } } @@ -9722,13 +9752,15 @@ namespace ts { if (getEnumKind(symbol) === EnumKind.Literal) { enumCount++; const memberTypeList: Type[] = []; - for (const declaration of symbol.declarations) { - if (declaration.kind === SyntaxKind.EnumDeclaration) { - for (const member of (declaration).members) { - const value = getEnumMemberValue(member); - const memberType = getFreshTypeOfLiteralType(getLiteralType(value !== undefined ? value : 0, enumCount, getSymbolOfNode(member))); - getSymbolLinks(getSymbolOfNode(member)).declaredType = memberType; - memberTypeList.push(getRegularTypeOfLiteralType(memberType)); + if (symbol.declarations) { + for (const declaration of symbol.declarations) { + if (declaration.kind === SyntaxKind.EnumDeclaration) { + for (const member of (declaration).members) { + const value = getEnumMemberValue(member); + const memberType = getFreshTypeOfLiteralType(getLiteralType(value !== undefined ? value : 0, enumCount, getSymbolOfNode(member))); + getSymbolLinks(getSymbolOfNode(member)).declaredType = memberType; + memberTypeList.push(getRegularTypeOfLiteralType(memberType)); + } } } } @@ -12022,7 +12054,7 @@ namespace ts { } function getSignaturesOfSymbol(symbol: Symbol | undefined): Signature[] { - if (!symbol) return emptyArray; + if (!symbol || !symbol.declarations) return emptyArray; const result: Signature[] = []; for (let i = 0; i < symbol.declarations.length; i++) { const decl = symbol.declarations[i]; @@ -12290,7 +12322,7 @@ namespace ts { function getIndexDeclarationOfSymbol(symbol: Symbol, kind: IndexKind): IndexSignatureDeclaration | undefined { const syntaxKind = kind === IndexKind.Number ? SyntaxKind.NumberKeyword : SyntaxKind.StringKeyword; const indexSymbol = getIndexSymbol(symbol); - if (indexSymbol) { + if (indexSymbol?.declarations) { for (const decl of indexSymbol.declarations) { const node = cast(decl, isIndexSignatureDeclaration); if (node.parameters.length === 1) { @@ -12324,7 +12356,7 @@ namespace ts { function getInferredTypeParameterConstraint(typeParameter: TypeParameter) { let inferences: Type[] | undefined; - if (typeParameter.symbol) { + if (typeParameter.symbol?.declarations) { for (const declaration of typeParameter.symbol.declarations) { if (declaration.parent.kind === SyntaxKind.InferType) { // When an 'infer T' declaration is immediately contained in a type reference node @@ -12839,12 +12871,14 @@ namespace ts { function getTypeDeclaration(symbol: Symbol): Declaration | undefined { const declarations = symbol.declarations; - for (const declaration of declarations) { - switch (declaration.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - return declaration; + if (declarations) { + for (const declaration of declarations) { + switch (declaration.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + return declaration; + } } } } @@ -14263,7 +14297,7 @@ namespace ts { if (propName !== undefined) { const prop = getPropertyOfType(objectType, propName); if (prop) { - if (reportDeprecated && accessNode && getDeclarationNodeFlagsFromSymbol(prop) & NodeFlags.Deprecated && isUncalledFunctionReference(accessNode, prop)) { + if (reportDeprecated && accessNode && prop.declarations && getDeclarationNodeFlagsFromSymbol(prop) & NodeFlags.Deprecated && isUncalledFunctionReference(accessNode, prop)) { const deprecatedNode = accessExpression?.argumentExpression ?? (isIndexedAccessTypeNode(accessNode) ? accessNode.indexType : accessNode); addDeprecatedSuggestion(deprecatedNode, prop.declarations, propName as string); } @@ -15143,7 +15177,7 @@ namespace ts { function isSpreadableProperty(prop: Symbol): boolean { return !some(prop.declarations, isPrivateIdentifierPropertyDeclaration) && (!(prop.flags & (SymbolFlags.Method | SymbolFlags.GetAccessor | SymbolFlags.SetAccessor)) || - !prop.declarations.some(decl => isClassLike(decl.parent))); + !prop.declarations?.some(decl => isClassLike(decl.parent))); } function getSpreadSymbol(prop: Symbol, readonly: boolean) { @@ -15589,7 +15623,7 @@ namespace ts { } function getObjectTypeInstantiation(type: AnonymousType | DeferredTypeReference, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) { - const declaration = type.objectFlags & ObjectFlags.Reference ? (type).node! : type.symbol.declarations[0]; + const declaration = type.objectFlags & ObjectFlags.Reference ? (type).node! : type.symbol.declarations![0]; const links = getNodeLinks(declaration); const target = type.objectFlags & ObjectFlags.Reference ? links.resolvedType! : type.objectFlags & ObjectFlags.Instantiated ? type.target! : type; @@ -16224,7 +16258,7 @@ namespace ts { if (resultObj.errors) { if (target.symbol && length(target.symbol.declarations)) { addRelatedInfo(resultObj.errors[resultObj.errors.length - 1], createDiagnosticForNode( - target.symbol.declarations[0], + target.symbol.declarations![0], Diagnostics.The_expected_type_comes_from_the_return_type_of_this_signature, )); } @@ -16321,7 +16355,7 @@ namespace ts { } if (!issuedElaboration && (targetProp && length(targetProp.declarations) || target.symbol && length(target.symbol.declarations))) { - const targetNode = targetProp && length(targetProp.declarations) ? targetProp.declarations[0] : target.symbol.declarations[0]; + const targetNode = targetProp && length(targetProp.declarations) ? targetProp.declarations![0] : target.symbol.declarations![0]; if (!getSourceFileOfNode(targetNode).hasNoDefaultLib) { addRelatedInfo(reportedDiag, createDiagnosticForNode( targetNode, @@ -17550,7 +17584,7 @@ namespace ts { } else { // use the property's value declaration if the property is assigned inside the literal itself - const objectLiteralDeclaration = source.symbol && firstOrUndefined(source.symbol.declarations); + const objectLiteralDeclaration = source.symbol?.declarations && firstOrUndefined(source.symbol.declarations); let suggestion: string | undefined; if (prop.valueDeclaration && findAncestor(prop.valueDeclaration, d => d === objectLiteralDeclaration) && getSourceFileOfNode(objectLiteralDeclaration) === getSourceFileOfNode(errorNode)) { const propDeclaration = prop.valueDeclaration as ObjectLiteralElementLike; @@ -18614,7 +18648,7 @@ namespace ts { const propName = symbolToString(unmatchedProperty); reportError(Diagnostics.Property_0_is_missing_in_type_1_but_required_in_type_2, propName, ...getTypeNamesForErrorDisplay(source, target)); if (length(unmatchedProperty.declarations)) { - associateRelatedInfo(createDiagnosticForNode(unmatchedProperty.declarations[0], Diagnostics._0_is_declared_here, propName)); + associateRelatedInfo(createDiagnosticForNode(unmatchedProperty.declarations![0], Diagnostics._0_is_declared_here, propName)); } if (shouldSkipElaboration && errorInfo) { overrideNextErrorInfo++; @@ -23560,7 +23594,7 @@ namespace ts { const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); const sourceSymbol = localOrExportSymbol.flags & SymbolFlags.Alias ? resolveAlias(localOrExportSymbol) : localOrExportSymbol; - if (getDeclarationNodeFlagsFromSymbol(sourceSymbol) & NodeFlags.Deprecated && isUncalledFunctionReference(node, sourceSymbol)) { + if (sourceSymbol.declarations && getDeclarationNodeFlagsFromSymbol(sourceSymbol) & NodeFlags.Deprecated && isUncalledFunctionReference(node, sourceSymbol)) { addDeprecatedSuggestion(node, sourceSymbol.declarations, node.escapedText as string); } @@ -23633,7 +23667,7 @@ namespace ts { } } else if (isAlias) { - declaration = find(symbol.declarations, isSomeImportDeclaration); + declaration = symbol.declarations?.find(isSomeImportDeclaration); } else { return type; @@ -26064,7 +26098,7 @@ namespace ts { else if (propertiesOfJsxElementAttribPropInterface.length === 1) { return propertiesOfJsxElementAttribPropInterface[0].escapedName; } - else if (propertiesOfJsxElementAttribPropInterface.length > 1) { + else if (propertiesOfJsxElementAttribPropInterface.length > 1 && jsxElementAttribPropInterfaceSym!.declarations) { // More than one property on ElementAttributesProperty is an error error(jsxElementAttribPropInterfaceSym!.declarations[0], Diagnostics.The_global_type_JSX_0_may_not_have_more_than_one_property, unescapeLeadingUnderscores(nameOfAttribPropContainer)); } @@ -26708,7 +26742,7 @@ namespace ts { } } else { - if (getDeclarationNodeFlagsFromSymbol(prop) & NodeFlags.Deprecated && isUncalledFunctionReference(node, prop)) { + if (prop.declarations && getDeclarationNodeFlagsFromSymbol(prop) & NodeFlags.Deprecated && isUncalledFunctionReference(node, prop)) { addDeprecatedSuggestion(right, prop.declarations, right.escapedText as string); } checkPropertyNotUsedBeforeDeclaration(prop, node, right); @@ -29353,7 +29387,7 @@ namespace ts { if (allowSyntheticDefaultImports && type && type !== errorType) { const synthType = type as SyntheticDefaultModuleType; if (!synthType.syntheticType) { - const file = find(originalSymbol.declarations, isSourceFile); + const file = originalSymbol.declarations?.find(isSourceFile); const hasSyntheticDefault = canHaveSyntheticDefault(file, originalSymbol, /*dontResolveAlias*/ false); if (hasSyntheticDefault) { const memberTable = createSymbolTable(); @@ -31304,7 +31338,7 @@ namespace ts { if (propType.symbol && propType.symbol.flags & SymbolFlags.Class) { const name = prop.escapedName; const symbol = resolveName(prop.valueDeclaration, name, SymbolFlags.Type, undefined, name, /*isUse*/ false); - if (symbol && symbol.declarations.some(isJSDocTypedefTag)) { + if (symbol?.declarations && symbol.declarations.some(isJSDocTypedefTag)) { addDuplicateDeclarationErrorsForSymbols(symbol, Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name), prop); addDuplicateDeclarationErrorsForSymbols(prop, Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name), symbol); } @@ -32480,7 +32514,7 @@ namespace ts { const nodeSymbol = getSymbolOfNode(node as InterfaceDeclaration); // in case of merging interface declaration it is possible that we'll enter this check procedure several times for every declaration // to prevent this run check only for the first declaration of a given kind - if (nodeSymbol.declarations.length > 0 && nodeSymbol.declarations[0] !== node) { + if (nodeSymbol.declarations && nodeSymbol.declarations.length > 0 && nodeSymbol.declarations[0] !== node) { return; } } @@ -32489,7 +32523,7 @@ namespace ts { // 3.7.4: An object type can contain at most one string index signature and one numeric index signature. // 8.5: A class declaration can have at most one string index member declaration and one numeric index member declaration const indexSymbol = getIndexSymbol(getSymbolOfNode(node)!); - if (indexSymbol) { + if (indexSymbol?.declarations) { let seenNumericIndexer = false; let seenStringIndexer = false; for (const decl of indexSymbol.declarations) { @@ -32760,7 +32794,7 @@ namespace ts { if (some(symbol.declarations, d => isTypeDeclaration(d) && !!(d.flags & NodeFlags.Deprecated))) { addDeprecatedSuggestion( getDeprecatedSuggestionNode(node), - symbol.declarations, + symbol.declarations!, symbol.escapedName as string ); } @@ -33111,59 +33145,61 @@ namespace ts { let multipleConstructorImplementation = false; let hasNonAmbientClass = false; const functionDeclarations = [] as Declaration[]; - for (const current of declarations) { - const node = current; - const inAmbientContext = node.flags & NodeFlags.Ambient; - const inAmbientContextOrInterface = node.parent && (node.parent.kind === SyntaxKind.InterfaceDeclaration || node.parent.kind === SyntaxKind.TypeLiteral) || inAmbientContext; - if (inAmbientContextOrInterface) { - // check if declarations are consecutive only if they are non-ambient - // 1. ambient declarations can be interleaved - // i.e. this is legal - // declare function foo(); - // declare function bar(); - // declare function foo(); - // 2. mixing ambient and non-ambient declarations is a separate error that will be reported - do not want to report an extra one - previousDeclaration = undefined; - } - - if ((node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression) && !inAmbientContext) { - hasNonAmbientClass = true; - } - - if (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.MethodSignature || node.kind === SyntaxKind.Constructor) { - functionDeclarations.push(node); - const currentNodeFlags = getEffectiveDeclarationFlags(node, flagsToCheck); - someNodeFlags |= currentNodeFlags; - allNodeFlags &= currentNodeFlags; - someHaveQuestionToken = someHaveQuestionToken || hasQuestionToken(node); - allHaveQuestionToken = allHaveQuestionToken && hasQuestionToken(node); - const bodyIsPresent = nodeIsPresent((node as FunctionLikeDeclaration).body); - - if (bodyIsPresent && bodyDeclaration) { - if (isConstructor) { - multipleConstructorImplementation = true; + if (declarations) { + for (const current of declarations) { + const node = current; + const inAmbientContext = node.flags & NodeFlags.Ambient; + const inAmbientContextOrInterface = node.parent && (node.parent.kind === SyntaxKind.InterfaceDeclaration || node.parent.kind === SyntaxKind.TypeLiteral) || inAmbientContext; + if (inAmbientContextOrInterface) { + // check if declarations are consecutive only if they are non-ambient + // 1. ambient declarations can be interleaved + // i.e. this is legal + // declare function foo(); + // declare function bar(); + // declare function foo(); + // 2. mixing ambient and non-ambient declarations is a separate error that will be reported - do not want to report an extra one + previousDeclaration = undefined; + } + + if ((node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression) && !inAmbientContext) { + hasNonAmbientClass = true; + } + + if (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.MethodSignature || node.kind === SyntaxKind.Constructor) { + functionDeclarations.push(node); + const currentNodeFlags = getEffectiveDeclarationFlags(node, flagsToCheck); + someNodeFlags |= currentNodeFlags; + allNodeFlags &= currentNodeFlags; + someHaveQuestionToken = someHaveQuestionToken || hasQuestionToken(node); + allHaveQuestionToken = allHaveQuestionToken && hasQuestionToken(node); + const bodyIsPresent = nodeIsPresent((node as FunctionLikeDeclaration).body); + + if (bodyIsPresent && bodyDeclaration) { + if (isConstructor) { + multipleConstructorImplementation = true; + } + else { + duplicateFunctionDeclaration = true; + } } - else { - duplicateFunctionDeclaration = true; + else if (previousDeclaration?.parent === node.parent && previousDeclaration.end !== node.pos) { + reportImplementationExpectedError(previousDeclaration); } - } - else if (previousDeclaration?.parent === node.parent && previousDeclaration.end !== node.pos) { - reportImplementationExpectedError(previousDeclaration); - } - if (bodyIsPresent) { - if (!bodyDeclaration) { - bodyDeclaration = node as FunctionLikeDeclaration; + if (bodyIsPresent) { + if (!bodyDeclaration) { + bodyDeclaration = node as FunctionLikeDeclaration; + } + } + else { + hasOverloads = true; } - } - else { - hasOverloads = true; - } - previousDeclaration = node; + previousDeclaration = node; - if (!inAmbientContextOrInterface) { - lastSeenNonAmbientDeclaration = node as FunctionLikeDeclaration; + if (!inAmbientContextOrInterface) { + lastSeenNonAmbientDeclaration = node as FunctionLikeDeclaration; + } } } } @@ -33197,8 +33233,10 @@ namespace ts { } if (hasOverloads) { - checkFlagAgreementBetweenOverloads(declarations, bodyDeclaration, flagsToCheck, someNodeFlags, allNodeFlags); - checkQuestionTokenAgreementBetweenOverloads(declarations, bodyDeclaration, someHaveQuestionToken, allHaveQuestionToken); + if (declarations) { + checkFlagAgreementBetweenOverloads(declarations, bodyDeclaration, flagsToCheck, someNodeFlags, allNodeFlags); + checkQuestionTokenAgreementBetweenOverloads(declarations, bodyDeclaration, someHaveQuestionToken, allHaveQuestionToken); + } if (bodyDeclaration) { const signatures = getSignaturesOfSymbol(symbol); @@ -33241,7 +33279,7 @@ namespace ts { let exportedDeclarationSpaces = DeclarationSpaces.None; let nonExportedDeclarationSpaces = DeclarationSpaces.None; let defaultExportedDeclarationSpaces = DeclarationSpaces.None; - for (const d of symbol.declarations) { + for (const d of symbol.declarations!) { const declarationSpaces = getDeclarationSpaces(d); const effectiveDeclarationFlags = getEffectiveDeclarationFlags(d, ModifierFlags.Export | ModifierFlags.Default); @@ -33266,7 +33304,7 @@ namespace ts { if (commonDeclarationSpacesForExportsAndLocals || commonDeclarationSpacesForDefaultAndNonDefault) { // declaration spaces for exported and non-exported declarations intersect - for (const d of symbol.declarations) { + for (const d of symbol.declarations!) { const declarationSpaces = getDeclarationSpaces(d); const name = getNameOfDeclaration(d); @@ -34002,7 +34040,7 @@ namespace ts { // Since the javascript won't do semantic analysis like typescript, // if the javascript file comes before the typescript file and both contain same name functions, // checkFunctionOrConstructorSymbol wouldn't be called if we didnt ignore javascript function. - const firstDeclaration = find(localSymbol.declarations, + const firstDeclaration = localSymbol.declarations?.find( // Get first non javascript function declaration declaration => declaration.kind === node.kind && !(declaration.flags & NodeFlags.JavaScriptFile)); @@ -34168,7 +34206,8 @@ namespace ts { function checkUnusedTypeParameters(node: ClassLikeDeclaration | SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration, addDiagnostic: AddUnusedDiagnostic): void { // Only report errors on the last declaration for the type parameter container; // this ensures that all uses have been accounted for. - if (last(getSymbolOfNode(node).declarations) !== node) return; + const declarations = getSymbolOfNode(node).declarations; + if (!declarations || last(declarations) !== node) return; const typeParameters = getEffectiveTypeParameterDeclarations(node); const seenParentsWithEveryUnused = new Set(); @@ -34243,39 +34282,41 @@ namespace ts { return; } - for (const declaration of local.declarations) { - if (isValidUnusedLocalDeclaration(declaration)) { - continue; - } + if (local.declarations) { + for (const declaration of local.declarations) { + if (isValidUnusedLocalDeclaration(declaration)) { + continue; + } - if (isImportedDeclaration(declaration)) { - addToGroup(unusedImports, importClauseFromImported(declaration), declaration, getNodeId); - } - else if (isBindingElement(declaration) && isObjectBindingPattern(declaration.parent)) { - // In `{ a, ...b }, `a` is considered used since it removes a property from `b`. `b` may still be unused though. - const lastElement = last(declaration.parent.elements); - if (declaration === lastElement || !last(declaration.parent.elements).dotDotDotToken) { - addToGroup(unusedDestructures, declaration.parent, declaration, getNodeId); + if (isImportedDeclaration(declaration)) { + addToGroup(unusedImports, importClauseFromImported(declaration), declaration, getNodeId); } - } - else if (isVariableDeclaration(declaration)) { - addToGroup(unusedVariables, declaration.parent, declaration, getNodeId); - } - else { - const parameter = local.valueDeclaration && tryGetRootParameterDeclaration(local.valueDeclaration); - const name = local.valueDeclaration && getNameOfDeclaration(local.valueDeclaration); - if (parameter && name) { - if (!isParameterPropertyDeclaration(parameter, parameter.parent) && !parameterIsThisKeyword(parameter) && !isIdentifierThatStartsWithUnderscore(name)) { - if (isBindingElement(declaration) && isArrayBindingPattern(declaration.parent)) { - addToGroup(unusedDestructures, declaration.parent, declaration, getNodeId); - } - else { - addDiagnostic(parameter, UnusedKind.Parameter, createDiagnosticForNode(name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(local))); - } + else if (isBindingElement(declaration) && isObjectBindingPattern(declaration.parent)) { + // In `{ a, ...b }, `a` is considered used since it removes a property from `b`. `b` may still be unused though. + const lastElement = last(declaration.parent.elements); + if (declaration === lastElement || !last(declaration.parent.elements).dotDotDotToken) { + addToGroup(unusedDestructures, declaration.parent, declaration, getNodeId); } } + else if (isVariableDeclaration(declaration)) { + addToGroup(unusedVariables, declaration.parent, declaration, getNodeId); + } else { - errorUnusedLocal(declaration, symbolName(local), addDiagnostic); + const parameter = local.valueDeclaration && tryGetRootParameterDeclaration(local.valueDeclaration); + const name = local.valueDeclaration && getNameOfDeclaration(local.valueDeclaration); + if (parameter && name) { + if (!isParameterPropertyDeclaration(parameter, parameter.parent) && !parameterIsThisKeyword(parameter) && !isIdentifierThatStartsWithUnderscore(name)) { + if (isBindingElement(declaration) && isArrayBindingPattern(declaration.parent)) { + addToGroup(unusedDestructures, declaration.parent, declaration, getNodeId); + } + else { + addDiagnostic(parameter, UnusedKind.Parameter, createDiagnosticForNode(name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(local))); + } + } + } + else { + errorUnusedLocal(declaration, symbolName(local), addDiagnostic); + } } } } @@ -34678,7 +34719,7 @@ namespace ts { checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(initializer), type, node, initializer, /*headMessage*/ undefined); } } - if (symbol.declarations.length > 1) { + if (symbol.declarations && symbol.declarations.length > 1) { if (some(symbol.declarations, d => d !== node && isVariableLike(d) && !areDeclarationFlagsIdentical(d, node))) { error(node.name, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name)); } @@ -36050,7 +36091,7 @@ namespace ts { // condition 'errorNode === undefined' may appear if types does not declare nor string neither number indexer if (!errorNode && (getObjectFlags(type) & ObjectFlags.Interface)) { const someBaseTypeHasBothIndexers = forEach(getBaseTypes(type), base => getIndexTypeOfType(base, IndexKind.String) && getIndexTypeOfType(base, IndexKind.Number)); - errorNode = someBaseTypeHasBothIndexers ? undefined : type.symbol.declarations[0]; + errorNode = someBaseTypeHasBothIndexers || !type.symbol.declarations ? undefined : type.symbol.declarations[0]; } } @@ -36101,7 +36142,7 @@ namespace ts { // check if any base class already has both property and indexer. // check should be performed only if 'type' is the first type that brings property\indexer together const someBaseClassHasBothPropertyAndIndexer = forEach(getBaseTypes(containingType), base => getPropertyOfObjectType(base, prop.escapedName) && getIndexTypeOfType(base, indexKind)); - errorNode = someBaseClassHasBothPropertyAndIndexer ? undefined : containingType.symbol.declarations[0]; + errorNode = someBaseClassHasBothPropertyAndIndexer || !containingType.symbol.declarations ? undefined : containingType.symbol.declarations[0]; } if (errorNode && !isTypeAssignableTo(propertyType, indexType)) { @@ -36189,7 +36230,7 @@ namespace ts { /** Check that type parameter lists are identical across multiple declarations */ function checkTypeParameterListsIdentical(symbol: Symbol) { - if (symbol.declarations.length === 1) { + if (symbol.declarations && symbol.declarations.length === 1) { return; } @@ -36197,7 +36238,7 @@ namespace ts { if (!links.typeParametersChecked) { links.typeParametersChecked = true; const declarations = getClassOrInterfaceDeclarationsOfSymbol(symbol); - if (declarations.length <= 1) { + if (!declarations || declarations.length <= 1) { return; } @@ -36549,12 +36590,12 @@ namespace ts { error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, symbolToString(base), typeToString(baseType), typeToString(type)); } else if (compilerOptions.useDefineForClassFields) { - const uninitialized = find(derived.declarations, d => d.kind === SyntaxKind.PropertyDeclaration && !(d as PropertyDeclaration).initializer); + const uninitialized = derived.declarations?.find(d => d.kind === SyntaxKind.PropertyDeclaration && !(d as PropertyDeclaration).initializer); if (uninitialized && !(derived.flags & SymbolFlags.Transient) && !(baseDeclarationFlags & ModifierFlags.Abstract) && !(derivedDeclarationFlags & ModifierFlags.Abstract) - && !derived.declarations.some(d => !!(d.flags & NodeFlags.Ambient))) { + && !derived.declarations?.some(d => !!(d.flags & NodeFlags.Ambient))) { const constructor = findConstructorDeclaration(getClassLikeDeclarationOfSymbol(type.symbol)!); const propName = (uninitialized as PropertyDeclaration).name; if ((uninitialized as PropertyDeclaration).exclamationToken @@ -36945,7 +36986,7 @@ namespace ts { const enumSymbol = getSymbolOfNode(node); const firstDeclaration = getDeclarationOfKind(enumSymbol, node.kind); if (node === firstDeclaration) { - if (enumSymbol.declarations.length > 1) { + if (enumSymbol.declarations && enumSymbol.declarations.length > 1) { const enumIsConst = isEnumConst(node); // check that const is placed\omitted on all enum declarations forEach(enumSymbol.declarations, decl => { @@ -36988,11 +37029,13 @@ namespace ts { function getFirstNonAmbientClassOrFunctionDeclaration(symbol: Symbol): Declaration | undefined { const declarations = symbol.declarations; - for (const declaration of declarations) { - if ((declaration.kind === SyntaxKind.ClassDeclaration || - (declaration.kind === SyntaxKind.FunctionDeclaration && nodeIsPresent((declaration).body))) && - !(declaration.flags & NodeFlags.Ambient)) { - return declaration; + if (declarations) { + for (const declaration of declarations) { + if ((declaration.kind === SyntaxKind.ClassDeclaration || + (declaration.kind === SyntaxKind.FunctionDeclaration && nodeIsPresent((declaration).body))) && + !(declaration.flags & NodeFlags.Ambient)) { + return declaration; + } } } return undefined; @@ -37047,6 +37090,7 @@ namespace ts { // The following checks only apply on a non-ambient instantiated module declaration. if (symbol.flags & SymbolFlags.ValueModule && !inAmbientContext + && symbol.declarations && symbol.declarations.length > 1 && isInstantiatedModule(node, shouldPreserveConstEnums(compilerOptions))) { const firstNonAmbientClassOrFunc = getFirstNonAmbientClassOrFunctionDeclaration(symbol); @@ -37156,7 +37200,7 @@ namespace ts { let reportError = !(symbol.flags & SymbolFlags.Transient); if (!reportError) { // symbol should not originate in augmentation - reportError = !!symbol.parent && isExternalModuleAugmentation(symbol.parent.declarations[0]); + reportError = !!symbol.parent?.declarations && isExternalModuleAugmentation(symbol.parent.declarations[0]); } } break; @@ -37459,7 +37503,7 @@ namespace ts { // find immediate value referenced by exported name (SymbolFlags.Alias is set so we don't chase down aliases) const symbol = resolveName(exportedName, exportedName.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); - if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) { + if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) { error(exportedName, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, idText(exportedName)); } else { @@ -37578,7 +37622,7 @@ namespace ts { return; } if (exportedDeclarationsCount > 1) { - for (const declaration of declarations) { + for (const declaration of declarations!) { if (isNotOverload(declaration)) { diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Cannot_redeclare_exported_variable_0, unescapeLeadingUnderscores(id))); } @@ -39419,10 +39463,12 @@ namespace ts { for (const s of arrayFrom(exports.values())) { if (s.mergeId) { const merged = getMergedSymbol(s); - for (const d of merged.declarations) { - const declFile = getSourceFileOfNode(d); - if (declFile === importTarget) { - return true; + if (merged.declarations) { + for (const d of merged.declarations) { + const declFile = getSourceFileOfNode(d); + if (declFile === importTarget) { + return true; + } } } } @@ -39455,15 +39501,12 @@ namespace ts { // defined here to avoid outer scope pollution function getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): string[] | undefined { // program does not have any files with type reference directives - bail out - if (!fileToDirective) { - return undefined; - } - if (!isSymbolFromTypeDeclarationFile(symbol)) { + if (!fileToDirective || !isSymbolFromTypeDeclarationFile(symbol)) { return undefined; } // check what declarations in the symbol can contribute to the target meaning let typeReferenceDirectives: string[] | undefined; - for (const decl of symbol.declarations) { + for (const decl of symbol.declarations!) { // check meaning of the local symbol to see if declaration needs to be analyzed further if (decl.symbol && decl.symbol.flags & meaning!) { const file = getSourceFileOfNode(decl); @@ -39553,7 +39596,7 @@ namespace ts { // It is an error for a non-external-module (i.e. script) to declare its own `globalThis`. // We can't use `builtinGlobals` for this due to synthetic expando-namespace generation in JS files. const fileGlobalThisSymbol = file.locals!.get("globalThis" as __String); - if (fileGlobalThisSymbol) { + if (fileGlobalThisSymbol?.declarations) { for (const declaration of fileGlobalThisSymbol.declarations) { diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0, "globalThis")); } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index e66ea4c2597bf..dafda8263ad89 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -292,7 +292,7 @@ namespace ts { return -1; } - export function countWhere(array: readonly T[], predicate: (x: T, i: number) => boolean): number { + export function countWhere(array: readonly T[] | undefined, predicate: (x: T, i: number) => boolean): number { let count = 0; if (array) { for (let i = 0; i < array.length; i++) { diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index e27f63eb29cda..c61980963278b 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -382,7 +382,7 @@ namespace ts.moduleSpecifiers { } function tryGetModuleNameFromAmbientModule(moduleSymbol: Symbol, checker: TypeChecker): string | undefined { - const decl = find(moduleSymbol.declarations, + const decl = moduleSymbol.declarations?.find( d => isNonGlobalAmbientModule(d) && (!isExternalModuleAugmentation(d) || !isExternalModuleNameRelative(getTextOfIdentifierOrLiteral(d.name))) ) as (ModuleDeclaration & { name: StringLiteral }) | undefined; if (decl) { diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 69add2945f4ba..e20d1ac0bfca2 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -206,13 +206,15 @@ namespace ts { } function reportNonlocalAugmentation(containingFile: SourceFile, parentSymbol: Symbol, symbol: Symbol) { - const primaryDeclaration = find(parentSymbol.declarations, d => getSourceFileOfNode(d) === containingFile)!; + const primaryDeclaration = parentSymbol.declarations?.find(d => getSourceFileOfNode(d) === containingFile)!; const augmentingDeclarations = filter(symbol.declarations, d => getSourceFileOfNode(d) !== containingFile); - for (const augmentations of augmentingDeclarations) { - context.addDiagnostic(addRelatedInfo( - createDiagnosticForNode(augmentations, Diagnostics.Declaration_augments_declaration_in_another_file_This_cannot_be_serialized), - createDiagnosticForNode(primaryDeclaration, Diagnostics.This_is_the_declaration_being_augmented_Consider_moving_the_augmenting_declaration_into_the_same_file) - )); + if (augmentingDeclarations) { + for (const augmentations of augmentingDeclarations) { + context.addDiagnostic(addRelatedInfo( + createDiagnosticForNode(augmentations, Diagnostics.Declaration_augments_declaration_in_another_file_This_cannot_be_serialized), + createDiagnosticForNode(primaryDeclaration, Diagnostics.This_is_the_declaration_being_augmented_Consider_moving_the_augmenting_declaration_into_the_same_file) + )); + } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index db5c8c9dba97d..8cfddadc7acaa 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4714,7 +4714,7 @@ namespace ts { export interface Symbol { flags: SymbolFlags; // Symbol flags escapedName: __String; // Name of symbol - declarations: Declaration[]; // Declarations associated with this symbol + declarations?: Declaration[]; // Declarations associated with this symbol valueDeclaration: Declaration; // First value declaration of the symbol members?: SymbolTable; // Class, interface or object literal instance members exports?: SymbolTable; // Module exports diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 27634c39a2ed4..f3c08902f55ff 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -767,7 +767,7 @@ namespace ts { } export function getNonAugmentationDeclaration(symbol: Symbol) { - return find(symbol.declarations, d => !isExternalModuleAugmentation(d) && !(isModuleDeclaration(d) && isGlobalScopeAugmentation(d))); + return symbol.declarations?.find(d => !isExternalModuleAugmentation(d) && !(isModuleDeclaration(d) && isGlobalScopeAugmentation(d))); } export function isEffectiveExternalModule(node: SourceFile, compilerOptions: CompilerOptions) { @@ -4888,7 +4888,7 @@ namespace ts { } export function getLocalSymbolForExportDefault(symbol: Symbol) { - if (!isExportDefaultSymbol(symbol)) return undefined; + if (!isExportDefaultSymbol(symbol) || !symbol.declarations) return undefined; for (const decl of symbol.declarations) { if (decl.localSymbol) return decl.localSymbol; } @@ -4896,7 +4896,7 @@ namespace ts { } function isExportDefaultSymbol(symbol: Symbol): boolean { - return symbol && length(symbol.declarations) > 0 && hasSyntacticModifier(symbol.declarations[0], ModifierFlags.Default); + return symbol && length(symbol.declarations) > 0 && hasSyntacticModifier(symbol.declarations![0], ModifierFlags.Default); } /** Return ".ts", ".d.ts", or ".tsx", if that is the extension. */ @@ -5445,7 +5445,7 @@ namespace ts { } export function getClassLikeDeclarationOfSymbol(symbol: Symbol): ClassLikeDeclaration | undefined { - return find(symbol.declarations, isClassLike); + return symbol.declarations?.find(isClassLike); } export function getObjectFlags(type: Type): ObjectFlags { diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 7c48ffbec689f..88237aea5a25d 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -1003,8 +1003,8 @@ namespace FourSlash { private verifySymbol(symbol: ts.Symbol, declarationRanges: Range[]) { const { declarations } = symbol; - if (declarations.length !== declarationRanges.length) { - this.raiseError(`Expected to get ${declarationRanges.length} declarations, got ${declarations.length}`); + if (declarations?.length !== declarationRanges.length) { + this.raiseError(`Expected to get ${declarationRanges.length} declarations, got ${declarations?.length}`); } ts.zipWith(declarations, declarationRanges, (decl, range) => { diff --git a/src/services/callHierarchy.ts b/src/services/callHierarchy.ts index ca5a193e78f9c..2c2a83a22cba2 100644 --- a/src/services/callHierarchy.ts +++ b/src/services/callHierarchy.ts @@ -181,7 +181,7 @@ namespace ts.CallHierarchy { const indices = indicesOf(symbol.declarations); const keys = map(symbol.declarations, decl => ({ file: decl.getSourceFile().fileName, pos: decl.pos })); indices.sort((a, b) => compareStringsCaseSensitive(keys[a].file, keys[b].file) || keys[a].pos - keys[b].pos); - const sortedDeclarations = map(indices, i => symbol.declarations[i]); + const sortedDeclarations = map(indices, i => symbol.declarations![i]); let lastDecl: CallHierarchyDeclaration | undefined; for (const decl of sortedDeclarations) { if (isValidCallHierarchyDeclaration(decl)) { diff --git a/src/services/codefixes/convertFunctionToEs6Class.ts b/src/services/codefixes/convertFunctionToEs6Class.ts index 986eb07c53c07..500e4346cad6a 100644 --- a/src/services/codefixes/convertFunctionToEs6Class.ts +++ b/src/services/codefixes/convertFunctionToEs6Class.ts @@ -61,7 +61,7 @@ namespace ts.codefix { // all static members are stored in the "exports" array of symbol if (symbol.exports) { symbol.exports.forEach(member => { - if (member.name === "prototype") { + if (member.name === "prototype" && member.declarations) { const firstDeclaration = member.declarations[0]; // only one "x.prototype = { ... }" will pass if (member.declarations.length === 1 && diff --git a/src/services/codefixes/generateAccessors.ts b/src/services/codefixes/generateAccessors.ts index 99bd53e29343f..fbcfd1b6943aa 100644 --- a/src/services/codefixes/generateAccessors.ts +++ b/src/services/codefixes/generateAccessors.ts @@ -262,7 +262,7 @@ namespace ts.codefix { const superSymbol = superElement && checker.getSymbolAtLocation(superElement.expression); if (!superSymbol) break; const symbol = superSymbol.flags & SymbolFlags.Alias ? checker.getAliasedSymbol(superSymbol) : superSymbol; - const superDecl = find(symbol.declarations, isClassLike); + const superDecl = symbol.declarations && find(symbol.declarations, isClassLike); if (!superDecl) break; res.push(superDecl); decl = superDecl; diff --git a/src/services/completions.ts b/src/services/completions.ts index 7245f4734a754..91aab7306e3af 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -890,7 +890,7 @@ namespace ts.Completions { } function isModuleSymbol(symbol: Symbol): boolean { - return symbol.declarations.some(d => d.kind === SyntaxKind.SourceFile); + return !!symbol.declarations?.some(d => d.kind === SyntaxKind.SourceFile); } function getCompletionData( @@ -1240,7 +1240,7 @@ namespace ts.Completions { const isValidAccess: (symbol: Symbol) => boolean = isNamespaceName // At `namespace N.M/**/`, if this is the only declaration of `M`, don't include `M` as a completion. - ? symbol => !!(symbol.flags & SymbolFlags.Namespace) && !symbol.declarations.every(d => d.parent === node.parent) + ? symbol => !!(symbol.flags & SymbolFlags.Namespace) && !symbol.declarations?.every(d => d.parent === node.parent) : isRhsOfImportDeclaration ? // Any kind is allowed when dotting off namespace in internal import equals declaration symbol => isValidTypeAccess(symbol) || isValidValueAccess(symbol) : diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index ab4ff4b2d4735..c4f6c8dea756a 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -839,7 +839,7 @@ namespace ts.FindAllReferences { } const exported = symbol.exports!.get(InternalSymbolName.ExportEquals); - if (exported) { + if (exported?.declarations) { for (const decl of exported.declarations) { const sourceFile = decl.getSourceFile(); if (sourceFilesSet.has(sourceFile.fileName)) { @@ -916,7 +916,7 @@ namespace ts.FindAllReferences { const result: SymbolAndEntries[] = []; const state = new State(sourceFiles, sourceFilesSet, node ? getSpecialSearchKind(node) : SpecialSearchKind.None, checker, cancellationToken, searchMeaning, options, result); - const exportSpecifier = !isForRenameWithPrefixAndSuffixText(options) ? undefined : find(symbol.declarations, isExportSpecifier); + const exportSpecifier = !isForRenameWithPrefixAndSuffixText(options) || !symbol.declarations ? undefined : find(symbol.declarations, isExportSpecifier); if (exportSpecifier) { // When renaming at an export specifier, rename the export and not the thing being exported. getReferencesAtExportSpecifier(exportSpecifier.name, symbol, exportSpecifier, state.createSearch(node, originalSymbol, /*comingFrom*/ undefined), state, /*addReferencesHere*/ true, /*alwaysGetReferences*/ true); diff --git a/src/services/getEditsForFileRename.ts b/src/services/getEditsForFileRename.ts index d2b17739824a0..563e10680522a 100644 --- a/src/services/getEditsForFileRename.ts +++ b/src/services/getEditsForFileRename.ts @@ -146,7 +146,7 @@ namespace ts { importLiteral => { const importedModuleSymbol = program.getTypeChecker().getSymbolAtLocation(importLiteral); // No need to update if it's an ambient module^M - if (importedModuleSymbol && importedModuleSymbol.declarations.some(d => isAmbientModule(d))) return undefined; + if (importedModuleSymbol?.declarations && importedModuleSymbol.declarations.some(d => isAmbientModule(d))) return undefined; const toImport = oldFromNew !== undefined // If we're at the new location (file was already renamed), need to redo module resolution starting from the old location. @@ -185,7 +185,7 @@ namespace ts { ): ToImport | undefined { if (importedModuleSymbol) { // `find` should succeed because we checked for ambient modules before calling this function. - const oldFileName = find(importedModuleSymbol.declarations, isSourceFile)!.fileName; + const oldFileName = find(importedModuleSymbol.declarations!, isSourceFile)!.fileName; const newFileName = oldToNew(oldFileName); return newFileName === undefined ? { newFileName: oldFileName, updated: false } : { newFileName, updated: true }; } diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index e1ea158f138f3..532fbbe9ba120 100644 --- a/src/services/goToDefinition.ts +++ b/src/services/goToDefinition.ts @@ -51,7 +51,7 @@ namespace ts.GoToDefinition { // assignment. This case and others are handled by the following code. if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(symbol.valueDeclaration); - const definitions = shorthandSymbol ? shorthandSymbol.declarations.map(decl => createDefinitionInfo(decl, typeChecker, shorthandSymbol, node)) : emptyArray; + const definitions = shorthandSymbol?.declarations ? shorthandSymbol.declarations.map(decl => createDefinitionInfo(decl, typeChecker, shorthandSymbol, node)) : emptyArray; return concatenate(definitions, getDefinitionFromObjectLiteralElement(typeChecker, node) || emptyArray); } @@ -206,7 +206,7 @@ namespace ts.GoToDefinition { // get the aliased symbol instead. This allows for goto def on an import e.g. // import {A, B} from "mod"; // to jump to the implementation directly. - if (symbol && symbol.flags & SymbolFlags.Alias && shouldSkipAlias(node, symbol.declarations[0])) { + if (symbol?.declarations && symbol.flags & SymbolFlags.Alias && shouldSkipAlias(node, symbol.declarations[0])) { const aliased = checker.getAliasedSymbol(symbol); if (aliased.declarations) { return aliased; @@ -254,7 +254,7 @@ namespace ts.GoToDefinition { // Applicable only if we are in a new expression, or we are on a constructor declaration // and in either case the symbol has a construct signature definition, i.e. class if (symbol.flags & SymbolFlags.Class && !(symbol.flags & (SymbolFlags.Function | SymbolFlags.Variable)) && (isNewExpressionTarget(node) || node.kind === SyntaxKind.ConstructorKeyword)) { - const cls = find(filteredDeclarations, isClassLike) || Debug.fail("Expected declaration to have at least one class-like declaration"); + const cls = find(filteredDeclarations!, isClassLike) || Debug.fail("Expected declaration to have at least one class-like declaration"); return getSignatureDefinition(cls.members, /*selectConstructors*/ true); } } diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts index d0256252ebcc5..d86e497330b28 100644 --- a/src/services/importTracker.ts +++ b/src/services/importTracker.ts @@ -62,9 +62,11 @@ namespace ts.FindAllReferences { } // Module augmentations may use this module's exports without importing it. - for (const decl of exportingModuleSymbol.declarations) { - if (isExternalModuleAugmentation(decl) && sourceFilesSet.has(decl.getSourceFile().fileName)) { - addIndirectUser(decl); + if (exportingModuleSymbol.declarations) { + for (const decl of exportingModuleSymbol.declarations) { + if (isExternalModuleAugmentation(decl) && sourceFilesSet.has(decl.getSourceFile().fileName)) { + addIndirectUser(decl); + } } } @@ -468,7 +470,7 @@ namespace ts.FindAllReferences { if (parent.kind === SyntaxKind.PropertyAccessExpression) { // When accessing an export of a JS module, there's no alias. The symbol will still be flagged as an export even though we're at the use. // So check that we are at the declaration. - return symbol.declarations.some(d => d === parent) && isBinaryExpression(grandParent) + return symbol.declarations?.some(d => d === parent) && isBinaryExpression(grandParent) ? getSpecialPropertyExport(grandParent, /*useLhsSymbol*/ false) : undefined; } diff --git a/src/services/refactors/convertOverloadListToSingleSignature.ts b/src/services/refactors/convertOverloadListToSingleSignature.ts index cd0e4d9cd8ef4..5c49bf06d0970 100644 --- a/src/services/refactors/convertOverloadListToSingleSignature.ts +++ b/src/services/refactors/convertOverloadListToSingleSignature.ts @@ -201,10 +201,10 @@ ${newComment.split("\n").map(c => ` * ${c}`).join("\n")} if (!every(decls, d => getSourceFileOfNode(d) === file)) { return; } - if (!isConvertableSignatureDeclaration(decls[0])) { + if (!isConvertableSignatureDeclaration(decls![0])) { return; } - const kindOne = decls[0].kind; + const kindOne = decls![0].kind; if (!every(decls, d => d.kind === kindOne)) { return; } diff --git a/src/services/refactors/convertParamsToDestructuredObject.ts b/src/services/refactors/convertParamsToDestructuredObject.ts index c9e587837ddd9..28bde59adaef6 100644 --- a/src/services/refactors/convertParamsToDestructuredObject.ts +++ b/src/services/refactors/convertParamsToDestructuredObject.ts @@ -360,7 +360,7 @@ namespace ts.refactor.convertParamsToDestructuredObject { if (isObjectLiteralExpression(functionDeclaration.parent)) { const contextualSymbol = getSymbolForContextualType(functionDeclaration.name, checker); // don't offer the refactor when there are multiple signatures since we won't know which ones the user wants to change - return contextualSymbol?.declarations.length === 1 && isSingleImplementation(functionDeclaration, checker); + return contextualSymbol?.declarations?.length === 1 && isSingleImplementation(functionDeclaration, checker); } return isSingleImplementation(functionDeclaration, checker); case SyntaxKind.Constructor: diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index ab85bb198cc62..e9cecdf6c9378 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -145,7 +145,7 @@ namespace ts.refactor { if (isTypeReferenceNode(node)) { if (isIdentifier(node.typeName)) { const symbol = checker.resolveName(node.typeName.text, node.typeName, SymbolFlags.TypeParameter, /* excludeGlobals */ true); - if (symbol) { + if (symbol?.declarations) { const declaration = cast(first(symbol.declarations), isTypeParameterDeclaration); if (rangeContainsSkipTrivia(statement, declaration, file) && !rangeContainsSkipTrivia(selection, declaration, file)) { pushIfUnique(result, declaration); diff --git a/src/services/refactors/moveToNewFile.ts b/src/services/refactors/moveToNewFile.ts index be93b513244e1..ad707f8d2e7ba 100644 --- a/src/services/refactors/moveToNewFile.ts +++ b/src/services/refactors/moveToNewFile.ts @@ -443,6 +443,9 @@ namespace ts.refactor { const oldFileNamedImports: string[] = []; const markSeenTop = nodeSeenTracker(); // Needed because multiple declarations may appear in `const x = 0, y = 1;`. newFileImportsFromOldFile.forEach(symbol => { + if (!symbol.declarations) { + return; + } for (const decl of symbol.declarations) { if (!isTopLevelDeclaration(decl)) continue; const name = nameOfTopLevelDeclaration(decl); diff --git a/src/services/rename.ts b/src/services/rename.ts index e75de5151cc5a..98e422cf01ea2 100644 --- a/src/services/rename.ts +++ b/src/services/rename.ts @@ -60,7 +60,7 @@ namespace ts.Rename { return getRenameInfoError(Diagnostics.You_cannot_rename_a_module_via_a_global_import); } - const moduleSourceFile = find(moduleSymbol.declarations, isSourceFile); + const moduleSourceFile = moduleSymbol.declarations && find(moduleSymbol.declarations, isSourceFile); if (!moduleSourceFile) return undefined; const withoutIndex = endsWith(node.text, "/index") || endsWith(node.text, "/index.js") ? undefined : tryRemoveSuffix(removeFileExtension(moduleSourceFile.fileName), "/index"); const name = withoutIndex === undefined ? moduleSourceFile.fileName : withoutIndex; diff --git a/src/services/symbolDisplay.ts b/src/services/symbolDisplay.ts index 19bfaba4499ef..589d4d4b8bf7b 100644 --- a/src/services/symbolDisplay.ts +++ b/src/services/symbolDisplay.ts @@ -401,8 +401,8 @@ namespace ts.SymbolDisplay { if (symbolFlags & SymbolFlags.EnumMember) { symbolKind = ScriptElementKind.enumMemberElement; addPrefixForAnyFunctionOrVar(symbol, "enum member"); - const declaration = symbol.declarations[0]; - if (declaration.kind === SyntaxKind.EnumMember) { + const declaration = symbol.declarations?.[0]; + if (declaration?.kind === SyntaxKind.EnumMember) { const constantValue = typeChecker.getConstantValue(declaration); if (constantValue !== undefined) { displayParts.push(spacePart()); @@ -446,22 +446,24 @@ namespace ts.SymbolDisplay { } } - switch (symbol.declarations[0].kind) { - case SyntaxKind.NamespaceExportDeclaration: - displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); - displayParts.push(spacePart()); - displayParts.push(keywordPart(SyntaxKind.NamespaceKeyword)); - break; - case SyntaxKind.ExportAssignment: - displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); - displayParts.push(spacePart()); - displayParts.push(keywordPart((symbol.declarations[0] as ExportAssignment).isExportEquals ? SyntaxKind.EqualsToken : SyntaxKind.DefaultKeyword)); - break; - case SyntaxKind.ExportSpecifier: - displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); - break; - default: - displayParts.push(keywordPart(SyntaxKind.ImportKeyword)); + if (symbol.declarations) { + switch (symbol.declarations[0].kind) { + case SyntaxKind.NamespaceExportDeclaration: + displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); + displayParts.push(spacePart()); + displayParts.push(keywordPart(SyntaxKind.NamespaceKeyword)); + break; + case SyntaxKind.ExportAssignment: + displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); + displayParts.push(spacePart()); + displayParts.push(keywordPart((symbol.declarations[0] as ExportAssignment).isExportEquals ? SyntaxKind.EqualsToken : SyntaxKind.DefaultKeyword)); + break; + case SyntaxKind.ExportSpecifier: + displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); + break; + default: + displayParts.push(keywordPart(SyntaxKind.ImportKeyword)); + } } displayParts.push(spacePart()); addFullSymbolName(symbol); @@ -556,7 +558,7 @@ namespace ts.SymbolDisplay { // For some special property access expressions like `exports.foo = foo` or `module.exports.foo = foo` // there documentation comments might be attached to the right hand side symbol of their declarations. // The pattern of such special property access is that the parent symbol is the symbol of the file. - if (symbol.parent && forEach(symbol.parent.declarations, declaration => declaration.kind === SyntaxKind.SourceFile)) { + if (symbol.parent && symbol.declarations && forEach(symbol.parent.declarations, declaration => declaration.kind === SyntaxKind.SourceFile)) { for (const declaration of symbol.declarations) { if (!declaration.parent || declaration.parent.kind !== SyntaxKind.BinaryExpression) { continue; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 062b2c787032c..157fab06969ba 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2408,7 +2408,7 @@ declare namespace ts { export interface Symbol { flags: SymbolFlags; escapedName: __String; - declarations: Declaration[]; + declarations?: Declaration[]; valueDeclaration: Declaration; members?: SymbolTable; exports?: SymbolTable; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index b2f6ada552140..25e9f24c6224d 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2408,7 +2408,7 @@ declare namespace ts { export interface Symbol { flags: SymbolFlags; escapedName: __String; - declarations: Declaration[]; + declarations?: Declaration[]; valueDeclaration: Declaration; members?: SymbolTable; exports?: SymbolTable;