From d069dcd6b430ef1417bb035bbcd306e7b7e58d17 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 8 Jan 2020 07:12:27 -0800 Subject: [PATCH 1/7] WIP --- src/services/codefixes/importFixes.ts | 69 ++++++++------- src/services/codefixes/inferFromUsage.ts | 1 + src/services/completions.ts | 87 ++++--------------- src/services/utilities.ts | 84 ++++++++++++++++++ .../importNameCodeFixConvertTypeOnly1.ts | 13 +++ 5 files changed, 151 insertions(+), 103 deletions(-) create mode 100644 tests/cases/fourslash/importNameCodeFixConvertTypeOnly1.ts diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index e5e141407cb6c..8974d88a907e3 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -29,7 +29,7 @@ namespace ts.codefix { const addToNamespace: FixUseNamespaceImport[] = []; const importType: FixUseImportType[] = []; // Keys are import clause node IDs. - const addToExisting = createMap<{ readonly importClause: ImportClause, defaultImport: string | undefined; readonly namedImports: string[] }>(); + const addToExisting = createMap<{ readonly importClause: ImportClause, defaultImport: string | undefined; readonly namedImports: string[], canUseTypeOnlyImport: boolean }>(); const newImports = createNewImportMap(); eachDiagnostic(context, errorCodes, diag => { @@ -46,11 +46,11 @@ namespace ts.codefix { importType.push(fix); break; case ImportFixKind.AddToExisting: { - const { importClause, importKind } = fix; + const { importClause, importKind, canUseTypeOnlyImport } = fix; const key = String(getNodeId(importClause)); let entry = addToExisting.get(key); if (!entry) { - addToExisting.set(key, entry = { importClause, defaultImport: undefined, namedImports: [] }); + addToExisting.set(key, entry = { importClause, defaultImport: undefined, namedImports: [], canUseTypeOnlyImport }); } if (importKind === ImportKind.Named) { pushIfUnique(entry.namedImports, symbolName); @@ -96,8 +96,8 @@ namespace ts.codefix { for (const fix of importType) { addImportType(changes, sourceFile, fix, quotePreference); } - addToExisting.forEach(({ importClause, defaultImport, namedImports }) => { - doAddExistingFix(changes, sourceFile, importClause, defaultImport, namedImports); + addToExisting.forEach(({ importClause, defaultImport, namedImports, canUseTypeOnlyImport }) => { + doAddExistingFix(changes, sourceFile, importClause, defaultImport, namedImports, canUseTypeOnlyImport); }); newImports.forEach((imports, moduleSpecifier, typeOnly) => { addNewImports(changes, sourceFile, typeOnly, moduleSpecifier, quotePreference, imports); @@ -148,6 +148,7 @@ namespace ts.codefix { readonly kind: ImportFixKind.AddToExisting; readonly importClause: ImportClause; readonly importKind: ImportKind.Default | ImportKind.Named; + readonly canUseTypeOnlyImport: boolean; } interface FixAddNewImport { readonly kind: ImportFixKind.AddNew; @@ -168,15 +169,14 @@ namespace ts.codefix { interface SymbolExportInfo { readonly moduleSymbol: Symbol; readonly importKind: ImportKind; - /** If true, can't use an es6 import from a js file. */ - readonly exportedSymbolIsTypeOnly: boolean; + /** If type only, can't use an es6 import from a js file. */ + readonly meaning: SemanticMeaning; } /** Information needed to augment an existing import declaration. */ interface FixAddToExistingImportInfo { readonly declaration: AnyImportSyntax; readonly importKind: ImportKind; - readonly exportedSymbolIsTypeOnly: boolean; } export function getImportCompletionAction( @@ -184,6 +184,7 @@ namespace ts.codefix { moduleSymbol: Symbol, sourceFile: SourceFile, symbolName: string, + symbolMeaning: SemanticMeaning, host: LanguageServiceHost, program: Program, formatContext: formatting.FormatContext, @@ -194,7 +195,7 @@ namespace ts.codefix { Debug.assert(exportInfos.some(info => info.moduleSymbol === moduleSymbol), "Some exportInfo should match the specified moduleSymbol"); // We sort the best codefixes first, so taking `first` is best for completions. const moduleSpecifier = first(getNewImportInfos(program, sourceFile, position, exportInfos, host, preferences)).moduleSpecifier; - const fix = first(getFixForImport(exportInfos, symbolName, position, program, sourceFile, host, preferences)); + const fix = first(getFixForImport(exportInfos, symbolName, symbolMeaning, position, program, sourceFile, host, preferences)); return { moduleSpecifier, codeAction: codeFixActionToCodeAction(codeActionForFix({ host, formatContext, preferences }, sourceFile, symbolName, fix, getQuotePreference(sourceFile, preferences))) }; } @@ -212,25 +213,26 @@ namespace ts.codefix { const defaultInfo = getDefaultLikeExportInfo(importingFile, moduleSymbol, checker, compilerOptions); if (defaultInfo && defaultInfo.name === symbolName && skipAlias(defaultInfo.symbol, checker) === exportedSymbol) { - result.push({ moduleSymbol, importKind: defaultInfo.kind, exportedSymbolIsTypeOnly: isTypeOnlySymbol(defaultInfo.symbol, checker) }); + result.push({ moduleSymbol, importKind: defaultInfo.kind, meaning: getMeaningFromSymbol(defaultInfo.symbol, checker) }); } for (const exported of checker.getExportsOfModule(moduleSymbol)) { if (exported.name === symbolName && skipAlias(exported, checker) === exportedSymbol) { - result.push({ moduleSymbol, importKind: ImportKind.Named, exportedSymbolIsTypeOnly: isTypeOnlySymbol(exported, checker) }); + result.push({ moduleSymbol, importKind: ImportKind.Named, meaning: getMeaningFromSymbol(exported, checker) }); } } }); return result; } - function isTypeOnlySymbol(s: Symbol, checker: TypeChecker): boolean { - return !(skipAlias(s, checker).flags & SymbolFlags.Value); + function isTypeOnlyPosition(sourceFile: SourceFile, position: number) { + } function getFixForImport( exportInfos: readonly SymbolExportInfo[], symbolName: string, + symbolMeaning: SemanticMeaning, position: number | undefined, program: Program, sourceFile: SourceFile, @@ -270,14 +272,14 @@ namespace ts.codefix { }); } - function tryAddToExistingImport(existingImports: readonly FixAddToExistingImportInfo[]): FixAddToExistingImport | undefined { + function tryAddToExistingImport(existingImports: readonly FixAddToExistingImportInfo[], canUseTypeOnlyImport: boolean): FixAddToExistingImport | undefined { return firstDefined(existingImports, ({ declaration, importKind }): FixAddToExistingImport | undefined => { if (declaration.kind !== SyntaxKind.ImportDeclaration) return undefined; const { importClause } = declaration; if (!importClause) return undefined; const { name, namedBindings } = importClause; return importKind === ImportKind.Default && !name || importKind === ImportKind.Named && (!namedBindings || namedBindings.kind === SyntaxKind.NamedImports) - ? { kind: ImportFixKind.AddToExisting, importClause, importKind } + ? { kind: ImportFixKind.AddToExisting, importClause, importKind, canUseTypeOnlyImport } : undefined; }); } @@ -292,12 +294,12 @@ namespace ts.codefix { } } - function getExistingImportDeclarations({ moduleSymbol, importKind, exportedSymbolIsTypeOnly }: SymbolExportInfo, checker: TypeChecker, sourceFile: SourceFile): readonly FixAddToExistingImportInfo[] { + function getExistingImportDeclarations({ moduleSymbol, importKind, meaning }: SymbolExportInfo, checker: TypeChecker, sourceFile: SourceFile): readonly FixAddToExistingImportInfo[] { // Can't use an es6 import for a type in JS. - return exportedSymbolIsTypeOnly && isSourceFileJS(sourceFile) ? emptyArray : mapDefined(sourceFile.imports, moduleSpecifier => { + return meaning === SemanticMeaning.Type && isSourceFileJS(sourceFile) ? emptyArray : mapDefined(sourceFile.imports, moduleSpecifier => { const i = importFromModuleSpecifier(moduleSpecifier); return (i.kind === SyntaxKind.ImportDeclaration || i.kind === SyntaxKind.ImportEqualsDeclaration) - && checker.getSymbolAtLocation(moduleSpecifier) === moduleSymbol ? { declaration: i, importKind, exportedSymbolIsTypeOnly } : undefined; + && checker.getSymbolAtLocation(moduleSpecifier) === moduleSymbol ? { declaration: i, importKind, meaning } : undefined; }); } @@ -311,13 +313,13 @@ namespace ts.codefix { ): readonly (FixAddNewImport | FixUseImportType)[] { const isJs = isSourceFileJS(sourceFile); const { allowsImportingSpecifier } = createAutoImportFilter(sourceFile, program, host); - const choicesForEachExportingModule = flatMap(moduleSymbols, ({ moduleSymbol, importKind, exportedSymbolIsTypeOnly }) => + const choicesForEachExportingModule = flatMap(moduleSymbols, ({ moduleSymbol, importKind, meaning }) => moduleSpecifiers.getModuleSpecifiers(moduleSymbol, program.getCompilerOptions(), sourceFile, host, program.getSourceFiles(), preferences, program.redirectTargetsMap) .map((moduleSpecifier): FixAddNewImport | FixUseImportType => // `position` should only be undefined at a missing jsx namespace, in which case we shouldn't be looking for pure types. - exportedSymbolIsTypeOnly && isJs + meaning === SemanticMeaning.Type && isJs ? { kind: ImportFixKind.ImportType, moduleSpecifier, position: Debug.assertDefined(position, "position should be defined") } - : { kind: ImportFixKind.AddNew, moduleSpecifier, importKind, typeOnly: exportedSymbolIsTypeOnly })); + : { kind: ImportFixKind.AddNew, moduleSpecifier, importKind, typeOnly: meaning === SemanticMeaning.Type })); // Sort by presence in package.json, then shortest paths first return sort(choicesForEachExportingModule, (a, b) => { @@ -346,14 +348,14 @@ namespace ts.codefix { return existingDeclaration ? [existingDeclaration] : getNewImportInfos(program, sourceFile, position, exportInfos, host, preferences); } - function newImportInfoFromExistingSpecifier({ declaration, importKind, exportedSymbolIsTypeOnly }: FixAddToExistingImportInfo): FixAddNewImport | undefined { + function newImportInfoFromExistingSpecifier({ declaration, importKind }: FixAddToExistingImportInfo): FixAddNewImport | undefined { const expression = declaration.kind === SyntaxKind.ImportDeclaration ? declaration.moduleSpecifier : declaration.moduleReference.kind === SyntaxKind.ExternalModuleReference ? declaration.moduleReference.expression : undefined; return expression && isStringLiteral(expression) - ? { kind: ImportFixKind.AddNew, moduleSpecifier: expression.text, importKind, typeOnly: exportedSymbolIsTypeOnly } + ? { kind: ImportFixKind.AddNew, moduleSpecifier: expression.text, importKind, typeOnly: meaning === SemanticMeaning.Type } : undefined; } @@ -372,8 +374,8 @@ namespace ts.codefix { if (!umdSymbol) return undefined; const symbol = checker.getAliasedSymbol(umdSymbol); const symbolName = umdSymbol.name; - const exportInfos: readonly SymbolExportInfo[] = [{ moduleSymbol: symbol, importKind: getUmdImportKind(sourceFile, program.getCompilerOptions()), exportedSymbolIsTypeOnly: false }]; - const fixes = getFixForImport(exportInfos, symbolName, isIdentifier(token) ? token.getStart(sourceFile) : undefined, program, sourceFile, host, preferences); + const exportInfos: readonly SymbolExportInfo[] = [{ moduleSymbol: symbol, importKind: getUmdImportKind(sourceFile, program.getCompilerOptions()), meaning: SemanticMeaning.All }]; + const fixes = getFixForImport(exportInfos, symbolName, SemanticMeaning.All, isIdentifier(token) ? token.getStart(sourceFile) : undefined, program, sourceFile, host, preferences); return { fixes, symbolName }; } function getUmdSymbol(token: Node, checker: TypeChecker): Symbol | undefined { @@ -427,9 +429,10 @@ namespace ts.codefix { // "default" is a keyword and not a legal identifier for the import, so we don't expect it here Debug.assert(symbolName !== InternalSymbolName.Default, "'default' isn't a legal identifier and couldn't occur here"); - const exportInfos = getExportInfos(symbolName, getMeaningFromLocation(symbolToken), cancellationToken, sourceFile, checker, program, host); + const meaning = getMeaningFromLocation(symbolToken); + const exportInfos = getExportInfos(symbolName, meaning, cancellationToken, sourceFile, checker, program, host); const fixes = arrayFrom(flatMapIterator(exportInfos.entries(), ([_, exportInfos]) => - getFixForImport(exportInfos, symbolName, symbolToken.getStart(sourceFile), program, sourceFile, host, preferences))); + getFixForImport(exportInfos, symbolName, meaning, symbolToken.getStart(sourceFile), program, sourceFile, host, preferences))); return { fixes, symbolName }; } @@ -447,7 +450,7 @@ namespace ts.codefix { // Maps symbol id to info for modules providing that symbol (original export + re-exports). const originalSymbolToExportInfos = createMultiMap(); function addSymbol(moduleSymbol: Symbol, exportedSymbol: Symbol, importKind: ImportKind): void { - originalSymbolToExportInfos.add(getUniqueSymbolId(exportedSymbol, checker).toString(), { moduleSymbol, importKind, exportedSymbolIsTypeOnly: isTypeOnlySymbol(exportedSymbol, checker) }); + originalSymbolToExportInfos.add(getUniqueSymbolId(exportedSymbol, checker).toString(), { moduleSymbol, importKind, meaning: getMeaningFromSymbol(exportedSymbol, checker) }); } forEachExternalModuleToImportFrom(program, host, sourceFile, /*filterByPackageJson*/ true, moduleSymbol => { cancellationToken.throwIfCancellationRequested(); @@ -553,8 +556,8 @@ namespace ts.codefix { addImportType(changes, sourceFile, fix, quotePreference); return [Diagnostics.Change_0_to_1, symbolName, getImportTypePrefix(fix.moduleSpecifier, quotePreference) + symbolName]; case ImportFixKind.AddToExisting: { - const { importClause, importKind } = fix; - doAddExistingFix(changes, sourceFile, importClause, importKind === ImportKind.Default ? symbolName : undefined, importKind === ImportKind.Named ? [symbolName] : emptyArray); + const { importClause, importKind, canUseTypeOnlyImport } = fix; + doAddExistingFix(changes, sourceFile, importClause, importKind === ImportKind.Default ? symbolName : undefined, importKind === ImportKind.Named ? [symbolName] : emptyArray, canUseTypeOnlyImport); const moduleSpecifierWithoutQuotes = stripQuotes(importClause.parent.moduleSpecifier.getText()); return [importKind === ImportKind.Default ? Diagnostics.Add_default_import_0_to_existing_import_declaration_from_1 : Diagnostics.Add_0_to_existing_import_declaration_from_1, symbolName, moduleSpecifierWithoutQuotes]; // you too! } @@ -570,7 +573,11 @@ namespace ts.codefix { } } - function doAddExistingFix(changes: textChanges.ChangeTracker, sourceFile: SourceFile, clause: ImportClause, defaultImport: string | undefined, namedImports: readonly string[]): void { + function doAddExistingFix(changes: textChanges.ChangeTracker, sourceFile: SourceFile, clause: ImportClause, defaultImport: string | undefined, namedImports: readonly string[], canUseTypeOnlyImport: boolean): void { + if (!canUseTypeOnlyImport && clause.isTypeOnly) { + changes.replaceNode(sourceFile, clause, updateImportClause(clause, clause.name, clause.namedBindings, /*isTypeOnly*/ false)); + } + if (defaultImport) { Debug.assert(!clause.name, "Default imports can't have names"); changes.insertNodeAt(sourceFile, clause.getStart(sourceFile), createIdentifier(defaultImport), { suffix: ", " }); diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 5718f592cb82a..7910a38a433cf 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -322,6 +322,7 @@ namespace ts.codefix { moduleSymbol, sourceFile, symbol.name, + SemanticMeaning.Type, host, program, formatContext, diff --git a/src/services/completions.ts b/src/services/completions.ts index bdf97266cf959..deb502759ba7d 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -525,6 +525,7 @@ namespace ts.Completions { symbolToOriginInfoMap: SymbolOriginInfoMap; previousToken: Node | undefined; readonly isJsxInitializer: IsJsxInitializer; + readonly isTypeOnlyLocation: boolean; } function getSymbolCompletionFromEntryId( program: Program, @@ -543,7 +544,7 @@ namespace ts.Completions { return { type: "request", request: completionData }; } - const { symbols, literals, location, completionKind, symbolToOriginInfoMap, previousToken, isJsxInitializer } = completionData; + const { symbols, literals, location, completionKind, symbolToOriginInfoMap, previousToken, isJsxInitializer, isTypeOnlyLocation } = completionData; const literal = find(literals, l => completionNameForLiteral(l) === entryId.name); if (literal !== undefined) return { type: "literal", literal }; @@ -556,7 +557,7 @@ namespace ts.Completions { const origin = symbolToOriginInfoMap[getSymbolId(symbol)]; const info = getCompletionEntryDisplayNameForSymbol(symbol, compilerOptions.target!, origin, completionKind); return info && info.name === entryId.name && getSourceFromOrigin(origin) === entryId.source - ? { type: "symbol" as const, symbol, location, symbolToOriginInfoMap, previousToken, isJsxInitializer } + ? { type: "symbol" as const, symbol, location, symbolToOriginInfoMap, previousToken, isJsxInitializer, isTypeOnlyLocation } : undefined; }) || { type: "none" }; } @@ -613,8 +614,8 @@ namespace ts.Completions { } } case "symbol": { - const { symbol, location, symbolToOriginInfoMap, previousToken } = symbolCompletion; - const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(symbolToOriginInfoMap, symbol, program, typeChecker, host, compilerOptions, sourceFile, position, previousToken, formatContext, preferences); + const { symbol, location, symbolToOriginInfoMap, previousToken, isTypeOnlyLocation } = symbolCompletion; + const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(symbolToOriginInfoMap, symbol, program, typeChecker, host, compilerOptions, sourceFile, position, previousToken, formatContext, preferences, isTypeOnlyLocation); return createCompletionDetailsForSymbol(symbol, typeChecker, sourceFile, location!, cancellationToken, codeActions, sourceDisplay); // TODO: GH#18217 } case "literal": { @@ -661,6 +662,7 @@ namespace ts.Completions { previousToken: Node | undefined, formatContext: formatting.FormatContext, preferences: UserPreferences, + isTypeOnlyLocation: boolean, ): CodeActionsAndSourceDisplay { const symbolOriginInfo = symbolToOriginInfoMap[getSymbolId(symbol)]; if (!symbolOriginInfo || !originIsExport(symbolOriginInfo)) { @@ -674,6 +676,7 @@ namespace ts.Completions { moduleSymbol, sourceFile, getSymbolName(symbol, symbolOriginInfo, compilerOptions.target!), + isTypeOnlyLocation ? SemanticMeaning.Type | SemanticMeaning.Namespace : SemanticMeaning.All, host, program, formatContext, @@ -714,6 +717,7 @@ namespace ts.Completions { readonly isJsxInitializer: IsJsxInitializer; readonly insideJsDocTagTypeExpression: boolean; readonly symbolToSortTextMap: SymbolSortTextMap; + readonly isTypeOnlyLocation: boolean; } type Request = { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag } | { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag }; @@ -794,7 +798,7 @@ namespace ts.Completions { const typeChecker = program.getTypeChecker(); let start = timestamp(); - let currentToken = getTokenAtPosition(sourceFile, position); // TODO: GH#15853 + const currentToken = getTokenAtPosition(sourceFile, position); // TODO: GH#15853 // We will check for jsdoc comments with insideComment and getJsDocTagAtPosition. (TODO: that seems rather inefficient to check the same thing so many times.) log("getCompletionData: Get current token: " + (timestamp() - start)); @@ -839,20 +843,13 @@ namespace ts.Completions { // Completion should work inside certain JsDoc tags. For example: // /** @type {number | string} */ // Completion should work in the brackets - const tag = getJsDocTagAtPosition(currentToken, position); + const tag = getJSDocTagAtPosition(currentToken, position); if (tag) { if (tag.tagName.pos <= position && position <= tag.tagName.end) { return { kind: CompletionDataKind.JsDocTagName }; } - if (isTagWithTypeExpression(tag) && tag.typeExpression && tag.typeExpression.kind === SyntaxKind.JSDocTypeExpression) { - currentToken = getTokenAtPosition(sourceFile, position); - if (!currentToken || - (!isDeclarationName(currentToken) && - (currentToken.parent.kind !== SyntaxKind.JSDocPropertyTag || - (currentToken.parent).name !== currentToken))) { - // Use as type location if inside tag's type expression - insideJsDocTagTypeExpression = isCurrentlyEditingNode(tag.typeExpression); - } + if (isTypePositionInJSDocTag(tag, position, sourceFile)) { + insideJsDocTagTypeExpression = true; } if (!insideJsDocTagTypeExpression && isJSDocParameterTag(tag) && (nodeIsMissing(tag.name) || tag.name.pos <= position && position <= tag.name.end)) { return { kind: CompletionDataKind.JsDocParameterName, tag }; @@ -1012,6 +1009,7 @@ namespace ts.Completions { const symbolToOriginInfoMap: SymbolOriginInfoMap = []; const symbolToSortTextMap: SymbolSortTextMap = []; const importSuggestionsCache = host.getImportSuggestionsCache && host.getImportSuggestionsCache(); + const isTypeOnly = isTypeOnlyCompletion(); if (isRightOfDot || isRightOfQuestionDot) { getTypeScriptMemberSymbols(); @@ -1061,24 +1059,10 @@ namespace ts.Completions { previousToken, isJsxInitializer, insideJsDocTagTypeExpression, - symbolToSortTextMap + symbolToSortTextMap, + isTypeOnlyLocation: isTypeOnly }; - type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag; - - function isTagWithTypeExpression(tag: JSDocTag): tag is JSDocTagWithTypeExpression { - switch (tag.kind) { - case SyntaxKind.JSDocParameterTag: - case SyntaxKind.JSDocPropertyTag: - case SyntaxKind.JSDocReturnTag: - case SyntaxKind.JSDocTypeTag: - case SyntaxKind.JSDocTypedefTag: - return true; - default: - return false; - } - } - function getTypeScriptMemberSymbols(): void { // Right of dot member completion list completionKind = CompletionKind.PropertyAccess; @@ -1329,7 +1313,6 @@ namespace ts.Completions { const scopeNode = getScopeNode(contextToken, adjustedPosition, sourceFile) || sourceFile; isInSnippetScope = isSnippetScope(scopeNode); - const isTypeOnly = isTypeOnlyCompletion(); const symbolMeanings = (isTypeOnly ? SymbolFlags.None : SymbolFlags.Value) | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias; symbols = Debug.assertEachDefined(typeChecker.getSymbolsInScope(scopeNode, symbolMeanings), "getSymbolsInScope() should all be defined"); @@ -1447,40 +1430,6 @@ namespace ts.Completions { || isContextTokenTypeLocation(contextToken)); } - function isContextTokenValueLocation(contextToken: Node) { - return contextToken && - contextToken.kind === SyntaxKind.TypeOfKeyword && - (contextToken.parent.kind === SyntaxKind.TypeQuery || isTypeOfExpression(contextToken.parent)); - } - - function isContextTokenTypeLocation(contextToken: Node): boolean { - if (contextToken) { - const parentKind = contextToken.parent.kind; - switch (contextToken.kind) { - case SyntaxKind.ColonToken: - return parentKind === SyntaxKind.PropertyDeclaration || - parentKind === SyntaxKind.PropertySignature || - parentKind === SyntaxKind.Parameter || - parentKind === SyntaxKind.VariableDeclaration || - isFunctionLikeKind(parentKind); - - case SyntaxKind.EqualsToken: - return parentKind === SyntaxKind.TypeAliasDeclaration; - - case SyntaxKind.AsKeyword: - return parentKind === SyntaxKind.AsExpression; - - case SyntaxKind.LessThanToken: - return parentKind === SyntaxKind.TypeReference || - parentKind === SyntaxKind.TypeAssertionExpression; - - case SyntaxKind.ExtendsKeyword: - return parentKind === SyntaxKind.TypeParameter; - } - } - return false; - } - /** True if symbol is a type or a module containing at least one type. */ function symbolCanBeReferencedAtTypeLocation(symbol: Symbol, seenModules = createMap()): boolean { const sym = skipAlias(symbol.exportSymbol || symbol, typeChecker); @@ -2536,12 +2485,6 @@ namespace ts.Completions { return isIdentifier(node) ? node.originalKeywordKind || SyntaxKind.Unknown : node.kind; } - /** Get the corresponding JSDocTag node if the position is in a jsDoc comment */ - function getJsDocTagAtPosition(node: Node, position: number): JSDocTag | undefined { - const jsdoc = findAncestor(node, isJSDoc); - return jsdoc && jsdoc.tags && (rangeContainsPosition(jsdoc, position) ? findLast(jsdoc.tags, tag => tag.pos < position) : undefined); - } - function getPropertiesForObjectExpression(contextualType: Type, baseConstrainedType: Type | undefined, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] { const hasBaseType = baseConstrainedType && baseConstrainedType !== contextualType; const type = hasBaseType && !(baseConstrainedType!.flags & TypeFlags.AnyOrUnknown) diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 9d52fcf88b66e..f18cb38afd990 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -22,6 +22,21 @@ namespace ts { All = Value | Type | Namespace } + export function getMeaningFromSymbol(symbol: Symbol, checker: TypeChecker): SemanticMeaning { + const flags = skipAlias(symbol, checker).flags; + let meaning = SemanticMeaning.None; + if (flags & SymbolFlags.Type) { + meaning |= SemanticMeaning.Type; + } + if (flags & SymbolFlags.Namespace) { + meaning |= SemanticMeaning.Namespace; + } + if (flags & SymbolFlags.Value) { + meaning |= SemanticMeaning.Value; + } + return meaning; + } + export function getMeaningFromDeclaration(node: Node): SemanticMeaning { switch (node.kind) { case SyntaxKind.VariableDeclaration: @@ -127,6 +142,40 @@ namespace ts { return name && name.parent.kind === SyntaxKind.ImportEqualsDeclaration ? SemanticMeaning.All : SemanticMeaning.Namespace; } + export function isContextTokenValueLocation(contextToken: Node) { + return contextToken && + contextToken.kind === SyntaxKind.TypeOfKeyword && + (contextToken.parent.kind === SyntaxKind.TypeQuery || isTypeOfExpression(contextToken.parent)); + } + + export function isContextTokenTypeLocation(contextToken: Node): boolean { + if (contextToken) { + const parentKind = contextToken.parent.kind; + switch (contextToken.kind) { + case SyntaxKind.ColonToken: + return parentKind === SyntaxKind.PropertyDeclaration || + parentKind === SyntaxKind.PropertySignature || + parentKind === SyntaxKind.Parameter || + parentKind === SyntaxKind.VariableDeclaration || + isFunctionLikeKind(parentKind); + + case SyntaxKind.EqualsToken: + return parentKind === SyntaxKind.TypeAliasDeclaration; + + case SyntaxKind.AsKeyword: + return parentKind === SyntaxKind.AsExpression; + + case SyntaxKind.LessThanToken: + return parentKind === SyntaxKind.TypeReference || + parentKind === SyntaxKind.TypeAssertionExpression; + + case SyntaxKind.ExtendsKeyword: + return parentKind === SyntaxKind.TypeParameter; + } + } + return false; + } + export function isInRightSideOfInternalImportEqualsDeclaration(node: Node) { while (node.parent.kind === SyntaxKind.QualifiedName) { node = node.parent; @@ -1137,6 +1186,41 @@ namespace ts { return !!findAncestor(token, isJSDoc); } + /** Get the corresponding JSDocTag node if the position is in a jsDoc comment */ + export function getJSDocTagAtPosition(node: Node, position: number): JSDocTag | undefined { + const jsdoc = findAncestor(node, isJSDoc); + return jsdoc && jsdoc.tags && (rangeContainsPosition(jsdoc, position) ? findLast(jsdoc.tags, tag => tag.pos < position) : undefined); + } + + export type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag; + + export function isTagWithTypeExpression(tag: JSDocTag): tag is JSDocTagWithTypeExpression { + switch (tag.kind) { + case SyntaxKind.JSDocParameterTag: + case SyntaxKind.JSDocPropertyTag: + case SyntaxKind.JSDocReturnTag: + case SyntaxKind.JSDocTypeTag: + case SyntaxKind.JSDocTypedefTag: + return true; + default: + return false; + } + } + + export function isTypePositionInJSDocTag(tag: JSDocTag, position: number, sourceFile: SourceFile): boolean { + if (isTagWithTypeExpression(tag) && tag.typeExpression && tag.typeExpression.kind === SyntaxKind.JSDocTypeExpression) { + const currentToken = getTokenAtPosition(sourceFile, position); + if (!currentToken || + (!isDeclarationName(currentToken) && + (currentToken.parent.kind !== SyntaxKind.JSDocPropertyTag || + (currentToken.parent).name !== currentToken))) { + // Use as type location if inside tag's type expression + return tag.typeExpression.getStart(sourceFile) <= position && position <= tag.typeExpression.getEnd(); + } + } + return false; + } + function nodeHasTokens(n: Node, sourceFile: SourceFileLike): boolean { // If we have a token or node that has a non-zero width, it must have tokens. // Note: getWidth() does not take trivia into account. diff --git a/tests/cases/fourslash/importNameCodeFixConvertTypeOnly1.ts b/tests/cases/fourslash/importNameCodeFixConvertTypeOnly1.ts new file mode 100644 index 0000000000000..15f94e550934e --- /dev/null +++ b/tests/cases/fourslash/importNameCodeFixConvertTypeOnly1.ts @@ -0,0 +1,13 @@ +/// + +// @Filename: /a.ts +//// export class A {} +//// export class B {} + +// @Filename: /b.ts +//// import type { A } from './a'; +//// new B + +goTo.file('/b.ts'); +verify.importFixAtPosition([`import { A, B } from './a'; +new B`]); From 9debd01546622c987bf0072bfa93d625982ab8fd Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 23 Jan 2020 16:16:16 -0800 Subject: [PATCH 2/7] Promote existing type-only imports to regular if needed --- src/compiler/checker.ts | 7 +- src/compiler/utilities.ts | 7 ++ src/services/codefixes/importFixes.ts | 123 ++++++++--------------- src/services/codefixes/inferFromUsage.ts | 1 - src/services/completions.ts | 6 +- 5 files changed, 52 insertions(+), 92 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 409d785a2274d..de91d5d6f1c88 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1869,12 +1869,7 @@ namespace ts { } function checkSymbolUsageInExpressionContext(symbol: Symbol, name: __String, useSite: Node) { - if ( - !(useSite.flags & NodeFlags.Ambient) && - !isPartOfTypeQuery(useSite) && - !isPartOfPossiblyValidTypeOrAbstractComputedPropertyName(useSite) && - isExpressionNode(useSite) - ) { + if (!isValidTypeOnlyAliasUseSite(useSite)) { const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(symbol); if (typeOnlyDeclaration) { const message = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 64b5ba7108cff..c14fd9fc17ea9 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -6134,4 +6134,11 @@ namespace ts { export function pseudoBigIntToString({negative, base10Value}: PseudoBigInt): string { return (negative && base10Value !== "0" ? "-" : "") + base10Value; } + + export function isValidTypeOnlyAliasUseSite(useSite: Node): boolean { + return !!(useSite.flags & NodeFlags.Ambient) + || isPartOfTypeQuery(useSite) + || isPartOfPossiblyValidTypeOrAbstractComputedPropertyName(useSite) + || !isExpressionNode(useSite); + } } diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index a06f70ed81c49..27ee6885daf65 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -31,7 +31,7 @@ namespace ts.codefix { const importType: FixUseImportType[] = []; // Keys are import clause node IDs. const addToExisting = createMap<{ readonly importClause: ImportClause, defaultImport: string | undefined; readonly namedImports: string[], canUseTypeOnlyImport: boolean }>(); - const newImports = createNewImportMap(); + const newImports = createMap>(); eachDiagnostic(context, errorCodes, diag => { const info = getFixesInfo(context, diag.code, diag.start); @@ -63,10 +63,10 @@ namespace ts.codefix { break; } case ImportFixKind.AddNew: { - const { moduleSpecifier, importKind, typeOnly } = fix; - let entry = newImports.get(moduleSpecifier, typeOnly); + const { moduleSpecifier, importKind } = fix; + let entry = newImports.get(moduleSpecifier); if (!entry) { - newImports.set(moduleSpecifier, typeOnly, entry = { defaultImport: undefined, namedImports: [], namespaceLikeImport: undefined }); + newImports.set(moduleSpecifier, entry = { defaultImport: undefined, namedImports: [], namespaceLikeImport: undefined }); } switch (importKind) { case ImportKind.Default: @@ -100,38 +100,13 @@ namespace ts.codefix { addToExisting.forEach(({ importClause, defaultImport, namedImports, canUseTypeOnlyImport }) => { doAddExistingFix(changes, sourceFile, importClause, defaultImport, namedImports, canUseTypeOnlyImport); }); - newImports.forEach((imports, moduleSpecifier, typeOnly) => { - addNewImports(changes, sourceFile, typeOnly, moduleSpecifier, quotePreference, imports); + newImports.forEach((imports, moduleSpecifier) => { + addNewImports(changes, sourceFile, moduleSpecifier, quotePreference, imports); }); })); }, }); - function createNewImportMap() { - // Keys are module specifiers. - const newImports = createMap>(); - return { - get: (moduleSpecifier: string, typeOnly: boolean) => { - return newImports.get(key(moduleSpecifier, typeOnly)); - }, - set: (moduleSpecifier: string, typeOnly: boolean, value: ImportsCollection) => { - return newImports.set(key(moduleSpecifier, typeOnly), value); - }, - forEach: (action: (value: ImportsCollection, moduleSpecifier: string, typeOnly: boolean) => void) => { - newImports.forEach((value, key) => { - const typeOnly = !!+key[0]; - const moduleSpecifier = key.slice(1); - action(value, moduleSpecifier, typeOnly); - }); - }, - }; - - function key(moduleSpecifier: string, typeOnly: boolean) { - const prefix = typeOnly ? "1" : "0"; - return prefix + moduleSpecifier; - } - } - // Sorted with the preferred fix coming first. const enum ImportFixKind { UseNamespace, ImportType, AddToExisting, AddNew } type ImportFix = FixUseNamespaceImport | FixUseImportType | FixAddToExistingImport | FixAddNewImport; @@ -155,7 +130,6 @@ namespace ts.codefix { readonly kind: ImportFixKind.AddNew; readonly moduleSpecifier: string; readonly importKind: ImportKind; - readonly typeOnly: boolean; } const enum ImportKind { @@ -170,8 +144,8 @@ namespace ts.codefix { interface SymbolExportInfo { readonly moduleSymbol: Symbol; readonly importKind: ImportKind; - /** If type only, can't use an es6 import from a js file. */ - readonly meaning: SemanticMeaning; + /** If true, can't use an es6 import from a js file. */ + readonly exportedSymbolIsTypeOnly: boolean; } /** Information needed to augment an existing import declaration. */ @@ -185,7 +159,6 @@ namespace ts.codefix { moduleSymbol: Symbol, sourceFile: SourceFile, symbolName: string, - symbolMeaning: SemanticMeaning, host: LanguageServiceHost, program: Program, formatContext: formatting.FormatContext, @@ -196,7 +169,7 @@ namespace ts.codefix { Debug.assert(exportInfos.some(info => info.moduleSymbol === moduleSymbol), "Some exportInfo should match the specified moduleSymbol"); // We sort the best codefixes first, so taking `first` is best for completions. const moduleSpecifier = first(getNewImportInfos(program, sourceFile, position, exportInfos, host, preferences)).moduleSpecifier; - const fix = first(getFixForImport(exportInfos, symbolName, symbolMeaning, position, program, sourceFile, host, preferences)); + const fix = first(getFixForImport(exportInfos, symbolName, position, program, sourceFile, host, preferences)); return { moduleSpecifier, codeAction: codeFixActionToCodeAction(codeActionForFix({ host, formatContext, preferences }, sourceFile, symbolName, fix, getQuotePreference(sourceFile, preferences))) }; } @@ -214,26 +187,30 @@ namespace ts.codefix { const defaultInfo = getDefaultLikeExportInfo(importingFile, moduleSymbol, checker, compilerOptions); if (defaultInfo && defaultInfo.name === symbolName && skipAlias(defaultInfo.symbol, checker) === exportedSymbol) { - result.push({ moduleSymbol, importKind: defaultInfo.kind, meaning: getMeaningFromSymbol(defaultInfo.symbol, checker) }); + result.push({ moduleSymbol, importKind: defaultInfo.kind, exportedSymbolIsTypeOnly: isTypeOnlySymbol(defaultInfo.symbol, checker) }); } for (const exported of checker.getExportsOfModule(moduleSymbol)) { if (exported.name === symbolName && skipAlias(exported, checker) === exportedSymbol) { - result.push({ moduleSymbol, importKind: ImportKind.Named, meaning: getMeaningFromSymbol(exported, checker) }); + result.push({ moduleSymbol, importKind: ImportKind.Named, exportedSymbolIsTypeOnly: isTypeOnlySymbol(exported, checker) }); } } }); return result; } - function isTypeOnlyPosition(sourceFile: SourceFile, position: number) { + function isTypeOnlySymbol(s: Symbol, checker: TypeChecker): boolean { + return !(skipAlias(s, checker).flags & SymbolFlags.Value); + } + function isTypeOnlyPosition(sourceFile: SourceFile, position: number) { + return isValidTypeOnlyAliasUseSite(getTokenAtPosition(sourceFile, position)); } function getFixForImport( exportInfos: readonly SymbolExportInfo[], symbolName: string, - symbolMeaning: SemanticMeaning, + /** undefined only for missing JSX namespace */ position: number | undefined, program: Program, sourceFile: SourceFile, @@ -243,7 +220,7 @@ namespace ts.codefix { const checker = program.getTypeChecker(); const existingImports = flatMap(exportInfos, info => getExistingImportDeclarations(info, checker, sourceFile)); const useNamespace = position === undefined ? undefined : tryUseExistingNamespaceImport(existingImports, symbolName, position, checker); - const addToExisting = tryAddToExistingImport(existingImports); + const addToExisting = tryAddToExistingImport(existingImports, position !== undefined && isTypeOnlyPosition(sourceFile, position)); // Don't bother providing an action to add a new import if we can add to an existing one. const addImport = addToExisting ? [addToExisting] : getFixesForAddImport(exportInfos, existingImports, program, sourceFile, position, host, preferences); return [...(useNamespace ? [useNamespace] : emptyArray), ...addImport]; @@ -295,12 +272,12 @@ namespace ts.codefix { } } - function getExistingImportDeclarations({ moduleSymbol, importKind, meaning }: SymbolExportInfo, checker: TypeChecker, sourceFile: SourceFile): readonly FixAddToExistingImportInfo[] { + function getExistingImportDeclarations({ moduleSymbol, importKind, exportedSymbolIsTypeOnly }: SymbolExportInfo, checker: TypeChecker, sourceFile: SourceFile): readonly FixAddToExistingImportInfo[] { // Can't use an es6 import for a type in JS. - return meaning === SemanticMeaning.Type && isSourceFileJS(sourceFile) ? emptyArray : mapDefined(sourceFile.imports, moduleSpecifier => { + return exportedSymbolIsTypeOnly && isSourceFileJS(sourceFile) ? emptyArray : mapDefined(sourceFile.imports, moduleSpecifier => { const i = importFromModuleSpecifier(moduleSpecifier); return (i.kind === SyntaxKind.ImportDeclaration || i.kind === SyntaxKind.ImportEqualsDeclaration) - && checker.getSymbolAtLocation(moduleSpecifier) === moduleSymbol ? { declaration: i, importKind, meaning } : undefined; + && checker.getSymbolAtLocation(moduleSpecifier) === moduleSymbol ? { declaration: i, importKind, exportedSymbolIsTypeOnly } : undefined; }); } @@ -314,13 +291,14 @@ namespace ts.codefix { ): readonly (FixAddNewImport | FixUseImportType)[] { const isJs = isSourceFileJS(sourceFile); const { allowsImportingSpecifier } = createAutoImportFilter(sourceFile, program, host); - const choicesForEachExportingModule = flatMap(moduleSymbols, ({ moduleSymbol, importKind, meaning }) => + + const choicesForEachExportingModule = flatMap(moduleSymbols, ({ moduleSymbol, importKind, exportedSymbolIsTypeOnly }) => moduleSpecifiers.getModuleSpecifiers(moduleSymbol, program.getCompilerOptions(), sourceFile, host, program.getSourceFiles(), preferences, program.redirectTargetsMap) .map((moduleSpecifier): FixAddNewImport | FixUseImportType => // `position` should only be undefined at a missing jsx namespace, in which case we shouldn't be looking for pure types. - meaning === SemanticMeaning.Type && isJs + exportedSymbolIsTypeOnly && isJs ? { kind: ImportFixKind.ImportType, moduleSpecifier, position: Debug.assertDefined(position, "position should be defined") } - : { kind: ImportFixKind.AddNew, moduleSpecifier, importKind, typeOnly: meaning === SemanticMeaning.Type })); + : { kind: ImportFixKind.AddNew, moduleSpecifier, importKind })); // Sort by presence in package.json, then shortest paths first return sort(choicesForEachExportingModule, (a, b) => { @@ -356,7 +334,7 @@ namespace ts.codefix { ? declaration.moduleReference.expression : undefined; return expression && isStringLiteral(expression) - ? { kind: ImportFixKind.AddNew, moduleSpecifier: expression.text, importKind, typeOnly: meaning === SemanticMeaning.Type } + ? { kind: ImportFixKind.AddNew, moduleSpecifier: expression.text, importKind } : undefined; } @@ -375,8 +353,8 @@ namespace ts.codefix { if (!umdSymbol) return undefined; const symbol = checker.getAliasedSymbol(umdSymbol); const symbolName = umdSymbol.name; - const exportInfos: readonly SymbolExportInfo[] = [{ moduleSymbol: symbol, importKind: getUmdImportKind(sourceFile, program.getCompilerOptions()), meaning: SemanticMeaning.All }]; - const fixes = getFixForImport(exportInfos, symbolName, SemanticMeaning.All, isIdentifier(token) ? token.getStart(sourceFile) : undefined, program, sourceFile, host, preferences); + const exportInfos: readonly SymbolExportInfo[] = [{ moduleSymbol: symbol, importKind: getUmdImportKind(sourceFile, program.getCompilerOptions()), exportedSymbolIsTypeOnly: false }]; + const fixes = getFixForImport(exportInfos, symbolName, isIdentifier(token) ? token.getStart(sourceFile) : undefined, program, sourceFile, host, preferences); return { fixes, symbolName }; } function getUmdSymbol(token: Node, checker: TypeChecker): Symbol | undefined { @@ -430,10 +408,9 @@ namespace ts.codefix { // "default" is a keyword and not a legal identifier for the import, so we don't expect it here Debug.assert(symbolName !== InternalSymbolName.Default, "'default' isn't a legal identifier and couldn't occur here"); - const meaning = getMeaningFromLocation(symbolToken); - const exportInfos = getExportInfos(symbolName, meaning, cancellationToken, sourceFile, checker, program, host); + const exportInfos = getExportInfos(symbolName, getMeaningFromLocation(symbolToken), cancellationToken, sourceFile, checker, program, host); const fixes = arrayFrom(flatMapIterator(exportInfos.entries(), ([_, exportInfos]) => - getFixForImport(exportInfos, symbolName, meaning, symbolToken.getStart(sourceFile), program, sourceFile, host, preferences))); + getFixForImport(exportInfos, symbolName, symbolToken.getStart(sourceFile), program, sourceFile, host, preferences))); return { fixes, symbolName }; } @@ -451,7 +428,7 @@ namespace ts.codefix { // Maps symbol id to info for modules providing that symbol (original export + re-exports). const originalSymbolToExportInfos = createMultiMap(); function addSymbol(moduleSymbol: Symbol, exportedSymbol: Symbol, importKind: ImportKind): void { - originalSymbolToExportInfos.add(getUniqueSymbolId(exportedSymbol, checker).toString(), { moduleSymbol, importKind, meaning: getMeaningFromSymbol(exportedSymbol, checker) }); + originalSymbolToExportInfos.add(getUniqueSymbolId(exportedSymbol, checker).toString(), { moduleSymbol, importKind, exportedSymbolIsTypeOnly: isTypeOnlySymbol(exportedSymbol, checker) }); } forEachExternalModuleToImportFrom(program, host, sourceFile, /*filterByPackageJson*/ true, moduleSymbol => { cancellationToken.throwIfCancellationRequested(); @@ -564,7 +541,7 @@ namespace ts.codefix { } case ImportFixKind.AddNew: { const { importKind, moduleSpecifier } = fix; - addNewImports(changes, sourceFile, fix.typeOnly, moduleSpecifier, quotePreference, importKind === ImportKind.Default ? { defaultImport: symbolName, namedImports: emptyArray, namespaceLikeImport: undefined } + addNewImports(changes, sourceFile, moduleSpecifier, quotePreference, importKind === ImportKind.Default ? { defaultImport: symbolName, namedImports: emptyArray, namespaceLikeImport: undefined } : importKind === ImportKind.Named ? { defaultImport: undefined, namedImports: [symbolName], namespaceLikeImport: undefined } : { defaultImport: undefined, namedImports: emptyArray, namespaceLikeImport: { importKind, name: symbolName } }); return [importKind === ImportKind.Default ? Diagnostics.Import_default_0_from_module_1 : Diagnostics.Import_0_from_module_1, symbolName, moduleSpecifier]; @@ -575,34 +552,18 @@ namespace ts.codefix { } function doAddExistingFix(changes: textChanges.ChangeTracker, sourceFile: SourceFile, clause: ImportClause, defaultImport: string | undefined, namedImports: readonly string[], canUseTypeOnlyImport: boolean): void { - if (!canUseTypeOnlyImport && clause.isTypeOnly) { - changes.replaceNode(sourceFile, clause, updateImportClause(clause, clause.name, clause.namedBindings, /*isTypeOnly*/ false)); - } - + const convertTypeOnlyToRegular = !canUseTypeOnlyImport && clause.isTypeOnly; + let name: Identifier | undefined; + let newNamedImports: NamedImports | undefined; if (defaultImport) { - Debug.assert(!clause.name, "Default imports can't have names"); - changes.insertNodeAt(sourceFile, clause.getStart(sourceFile), createIdentifier(defaultImport), { suffix: ", " }); + Debug.assert(!clause.name, "Cannot add a default import to an import clause that already has one"); + name = createIdentifier(defaultImport); } - if (namedImports.length) { const specifiers = namedImports.map(name => createImportSpecifier(/*propertyName*/ undefined, createIdentifier(name))); - if (clause.namedBindings && cast(clause.namedBindings, isNamedImports).elements.length) { - for (const spec of specifiers) { - changes.insertNodeInListAfter(sourceFile, last(cast(clause.namedBindings, isNamedImports).elements), spec); - } - } - else { - if (specifiers.length) { - const namedImports = createNamedImports(specifiers); - if (clause.namedBindings) { - changes.replaceNode(sourceFile, clause.namedBindings, namedImports); - } - else { - changes.insertNodeAfter(sourceFile, Debug.assertDefined(clause.name, "Named import specifiers must have names"), namedImports); - } - } - } + newNamedImports = createNamedImports(concatenate(tryCast(clause.namedBindings, isNamedImports)?.elements, specifiers)); } + changes.replaceNode(sourceFile, clause, updateImportClause(clause, name, newNamedImports, convertTypeOnlyToRegular ? false : clause.isTypeOnly)); } function addNamespaceQualifier(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { namespacePrefix, position }: FixUseNamespaceImport): void { @@ -626,13 +587,13 @@ namespace ts.codefix { readonly name: string; } | undefined; } - function addNewImports(changes: textChanges.ChangeTracker, sourceFile: SourceFile, typeOnly: boolean, moduleSpecifier: string, quotePreference: QuotePreference, { defaultImport, namedImports, namespaceLikeImport }: ImportsCollection): void { + function addNewImports(changes: textChanges.ChangeTracker, sourceFile: SourceFile, moduleSpecifier: string, quotePreference: QuotePreference, { defaultImport, namedImports, namespaceLikeImport }: ImportsCollection): void { const quotedModuleSpecifier = makeStringLiteral(moduleSpecifier, quotePreference); if (defaultImport !== undefined || namedImports.length) { insertImport(changes, sourceFile, makeImport( defaultImport === undefined ? undefined : createIdentifier(defaultImport), - namedImports.map(n => createImportSpecifier(/*propertyName*/ undefined, createIdentifier(n))), moduleSpecifier, quotePreference, typeOnly)); + namedImports.map(n => createImportSpecifier(/*propertyName*/ undefined, createIdentifier(n))), moduleSpecifier, quotePreference)); } if (namespaceLikeImport) { insertImport( @@ -640,7 +601,7 @@ namespace ts.codefix { sourceFile, namespaceLikeImport.importKind === ImportKind.Equals ? createImportEqualsDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createIdentifier(namespaceLikeImport.name), createExternalModuleReference(quotedModuleSpecifier)) : namespaceLikeImport.importKind === ImportKind.ConstEquals ? createConstEqualsRequireDeclaration(namespaceLikeImport.name, quotedModuleSpecifier) : - createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createImportClause(/*name*/ undefined, createNamespaceImport(createIdentifier(namespaceLikeImport.name)), typeOnly), quotedModuleSpecifier)); + createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createImportClause(/*name*/ undefined, createNamespaceImport(createIdentifier(namespaceLikeImport.name))), quotedModuleSpecifier)); } } diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 7910a38a433cf..5718f592cb82a 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -322,7 +322,6 @@ namespace ts.codefix { moduleSymbol, sourceFile, symbol.name, - SemanticMeaning.Type, host, program, formatContext, diff --git a/src/services/completions.ts b/src/services/completions.ts index ed83edca984d4..bbae2f4c0f298 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -614,8 +614,8 @@ namespace ts.Completions { } } case "symbol": { - const { symbol, location, symbolToOriginInfoMap, previousToken, isTypeOnlyLocation } = symbolCompletion; - const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(symbolToOriginInfoMap, symbol, program, typeChecker, host, compilerOptions, sourceFile, position, previousToken, formatContext, preferences, isTypeOnlyLocation); + const { symbol, location, symbolToOriginInfoMap, previousToken } = symbolCompletion; + const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(symbolToOriginInfoMap, symbol, program, typeChecker, host, compilerOptions, sourceFile, position, previousToken, formatContext, preferences); return createCompletionDetailsForSymbol(symbol, typeChecker, sourceFile, location!, cancellationToken, codeActions, sourceDisplay); // TODO: GH#18217 } case "literal": { @@ -662,7 +662,6 @@ namespace ts.Completions { previousToken: Node | undefined, formatContext: formatting.FormatContext, preferences: UserPreferences, - isTypeOnlyLocation: boolean, ): CodeActionsAndSourceDisplay { const symbolOriginInfo = symbolToOriginInfoMap[getSymbolId(symbol)]; if (!symbolOriginInfo || !originIsExport(symbolOriginInfo)) { @@ -676,7 +675,6 @@ namespace ts.Completions { moduleSymbol, sourceFile, getSymbolName(symbol, symbolOriginInfo, compilerOptions.target!), - isTypeOnlyLocation ? SemanticMeaning.Type | SemanticMeaning.Namespace : SemanticMeaning.All, host, program, formatContext, From 4ead5510ed2fd821d2b0717e2428e102090f0395 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 24 Jan 2020 09:57:51 -0800 Subject: [PATCH 3/7] Add completions test adding to type-only import --- .../fourslash/completionsImport_typeOnly.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/cases/fourslash/completionsImport_typeOnly.ts diff --git a/tests/cases/fourslash/completionsImport_typeOnly.ts b/tests/cases/fourslash/completionsImport_typeOnly.ts new file mode 100644 index 0000000000000..688afd5296625 --- /dev/null +++ b/tests/cases/fourslash/completionsImport_typeOnly.ts @@ -0,0 +1,24 @@ +/// + +// @target: esnext + +// @Filename: /a.ts +//// export class A {} +//// export class B {} + +// @Filename: /b.ts +//// import type { A } from './a'; +//// const b: B/**/ + +goTo.file('/b.ts'); +verify.applyCodeActionFromCompletion('', { + name: 'B', + source: '/a', + description: `Add 'B' to existing import declaration from "./a"`, + preferences: { + includeCompletionsForModuleExports: true, + includeInsertTextCompletions: true + }, + newFileContent: `import type { A, B } from './a'; +const b: B` +}); From b3d85a6fff31dc2d51e8c9d320afa9ebbd0a9a21 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 24 Jan 2020 11:14:14 -0800 Subject: [PATCH 4/7] Update tests, revert whole-import-clause replacement codefix strategy to preserve import specifier formatting --- src/services/codefixes/importFixes.ts | 27 +++++++++++++++---- src/services/utilities.ts | 9 +++++++ .../codeFixInferFromUsageContextualImport2.ts | 2 +- .../completionsImport_exportEquals.ts | 4 +-- .../importNameCodeFixNewImportFile3.ts | 2 +- .../importNameCodeFixNewImportFile4.ts | 2 +- .../importNameCodeFixNewImportTypeOnly.ts | 14 ---------- .../importNameCodeFix_exportEquals.ts | 4 +-- 8 files changed, 38 insertions(+), 26 deletions(-) delete mode 100644 tests/cases/fourslash/importNameCodeFixNewImportTypeOnly.ts diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 27ee6885daf65..ca3b6744b272f 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -553,17 +553,34 @@ namespace ts.codefix { function doAddExistingFix(changes: textChanges.ChangeTracker, sourceFile: SourceFile, clause: ImportClause, defaultImport: string | undefined, namedImports: readonly string[], canUseTypeOnlyImport: boolean): void { const convertTypeOnlyToRegular = !canUseTypeOnlyImport && clause.isTypeOnly; - let name: Identifier | undefined; - let newNamedImports: NamedImports | undefined; if (defaultImport) { Debug.assert(!clause.name, "Cannot add a default import to an import clause that already has one"); - name = createIdentifier(defaultImport); + changes.insertNodeAt(sourceFile, clause.getStart(sourceFile), createIdentifier(defaultImport), { suffix: ", " }); } + if (namedImports.length) { const specifiers = namedImports.map(name => createImportSpecifier(/*propertyName*/ undefined, createIdentifier(name))); - newNamedImports = createNamedImports(concatenate(tryCast(clause.namedBindings, isNamedImports)?.elements, specifiers)); + if (clause.namedBindings && cast(clause.namedBindings, isNamedImports).elements.length) { + for (const spec of specifiers) { + changes.insertNodeInListAfter(sourceFile, last(cast(clause.namedBindings, isNamedImports).elements), spec); + } + } + else { + if (specifiers.length) { + const namedImports = createNamedImports(specifiers); + if (clause.namedBindings) { + changes.replaceNode(sourceFile, clause.namedBindings, namedImports); + } + else { + changes.insertNodeAfter(sourceFile, Debug.assertDefined(clause.name, "Import clause must have either named imports or a default import"), namedImports); + } + } + } + } + + if (convertTypeOnlyToRegular) { + changes.delete(sourceFile, getTypeKeywordOfTypeOnlyImport(clause, sourceFile)); } - changes.replaceNode(sourceFile, clause, updateImportClause(clause, name, newNamedImports, convertTypeOnlyToRegular ? false : clause.isTypeOnly)); } function addNamespaceQualifier(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { namespacePrefix, position }: FixUseNamespaceImport): void { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 314cd8a66067c..6376a873d8706 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1390,6 +1390,10 @@ namespace ts { return contains(typeKeywords, kind); } + export function isTypeKeywordToken(node: Node): node is Token { + return node.kind === SyntaxKind.TypeKeyword; + } + /** True if the symbol is for an external module, as opposed to a namespace. */ export function isExternalModuleSymbol(moduleSymbol: Symbol): boolean { return !!(moduleSymbol.flags & SymbolFlags.Module) && moduleSymbol.name.charCodeAt(0) === CharacterCodes.doubleQuote; @@ -1578,6 +1582,11 @@ namespace ts { } } + export function getTypeKeywordOfTypeOnlyImport(importClause: ImportClause, sourceFile: SourceFile): Token { + Debug.assert(importClause.isTypeOnly); + return cast(importClause.getChildAt(0, sourceFile), isTypeKeywordToken); + } + export function textSpansEqual(a: TextSpan | undefined, b: TextSpan | undefined): boolean { return !!a && !!b && a.start === b.start && a.length === b.length; } diff --git a/tests/cases/fourslash/codeFixInferFromUsageContextualImport2.ts b/tests/cases/fourslash/codeFixInferFromUsageContextualImport2.ts index 8b88015bcfd50..f12f9dc9e80ed 100644 --- a/tests/cases/fourslash/codeFixInferFromUsageContextualImport2.ts +++ b/tests/cases/fourslash/codeFixInferFromUsageContextualImport2.ts @@ -20,7 +20,7 @@ goTo.file("/b.ts"); verify.codeFix({ description: "Infer parameter types from usage", newFileContent: -`import type { User } from "./a"; +`import { User } from "./a"; export function f(user: User) { getEmail(user); diff --git a/tests/cases/fourslash/completionsImport_exportEquals.ts b/tests/cases/fourslash/completionsImport_exportEquals.ts index 4a5d5fdd16d4d..7cd2e1cd2f4fd 100644 --- a/tests/cases/fourslash/completionsImport_exportEquals.ts +++ b/tests/cases/fourslash/completionsImport_exportEquals.ts @@ -43,7 +43,7 @@ verify.applyCodeActionFromCompletion("1", { source: "/a", description: `Import 'b' from module "./a"`, newFileContent: -`import type { b } from "./a"; +`import { b } from "./a"; a; let x: b;`, @@ -54,7 +54,7 @@ verify.applyCodeActionFromCompletion("0", { source: "/a", description: `Import 'a' from module "./a"`, newFileContent: -`import type { b } from "./a"; +`import { b } from "./a"; import a = require("./a"); a; diff --git a/tests/cases/fourslash/importNameCodeFixNewImportFile3.ts b/tests/cases/fourslash/importNameCodeFixNewImportFile3.ts index 30467e7654965..af7901e03e61c 100644 --- a/tests/cases/fourslash/importNameCodeFixNewImportFile3.ts +++ b/tests/cases/fourslash/importNameCodeFixNewImportFile3.ts @@ -9,7 +9,7 @@ //// } verify.importFixAtPosition([ -`import type { XXX } from "./module"; +`import { XXX } from "./module"; let t: XXX.I;` ]); \ No newline at end of file diff --git a/tests/cases/fourslash/importNameCodeFixNewImportFile4.ts b/tests/cases/fourslash/importNameCodeFixNewImportFile4.ts index 4d59eb28e5216..62cf48c4977b9 100644 --- a/tests/cases/fourslash/importNameCodeFixNewImportFile4.ts +++ b/tests/cases/fourslash/importNameCodeFixNewImportFile4.ts @@ -10,7 +10,7 @@ //// } verify.importFixAtPosition([ -`import type { A } from "./module"; +`import { A } from "./module"; let t: A.B.I;` ]); \ No newline at end of file diff --git a/tests/cases/fourslash/importNameCodeFixNewImportTypeOnly.ts b/tests/cases/fourslash/importNameCodeFixNewImportTypeOnly.ts deleted file mode 100644 index 3b2b61bf7a597..0000000000000 --- a/tests/cases/fourslash/importNameCodeFixNewImportTypeOnly.ts +++ /dev/null @@ -1,14 +0,0 @@ -/// - -// @target: esnext - -// @Filename: /a.ts -////export interface Car {} - -// @Filename: /b.ts -////function drive(car: /**/Car) {} - -goTo.file("/b.ts"); -verify.importFixAtPosition([`import type { Car } from "./a"; - -function drive(car: Car) {}`]); diff --git a/tests/cases/fourslash/importNameCodeFix_exportEquals.ts b/tests/cases/fourslash/importNameCodeFix_exportEquals.ts index 5dd9b9f100e7c..4ab87df7e7618 100644 --- a/tests/cases/fourslash/importNameCodeFix_exportEquals.ts +++ b/tests/cases/fourslash/importNameCodeFix_exportEquals.ts @@ -16,9 +16,9 @@ verify.codeFixAll({ fixId: "fixMissingImport", fixAllDescription: "Add all missing imports", newFileContent: -`import a = require("./a"); +`import { b } from "./a"; -import type { b } from "./a"; +import a = require("./a"); a; let x: b;`, From 68498b8e149147b3a6d1009d016aadf53fe3a926 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 27 Jan 2020 11:52:11 -0800 Subject: [PATCH 5/7] Revert unnecessary changes --- src/services/completions.ts | 69 ++++++++++++++++++++++++++++++++++--- src/services/utilities.ts | 69 ------------------------------------- 2 files changed, 65 insertions(+), 73 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index bbae2f4c0f298..98da3728de84b 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -796,7 +796,7 @@ namespace ts.Completions { const typeChecker = program.getTypeChecker(); let start = timestamp(); - const currentToken = getTokenAtPosition(sourceFile, position); // TODO: GH#15853 + let currentToken = getTokenAtPosition(sourceFile, position); // TODO: GH#15853 // We will check for jsdoc comments with insideComment and getJsDocTagAtPosition. (TODO: that seems rather inefficient to check the same thing so many times.) log("getCompletionData: Get current token: " + (timestamp() - start)); @@ -841,13 +841,19 @@ namespace ts.Completions { // Completion should work inside certain JsDoc tags. For example: // /** @type {number | string} */ // Completion should work in the brackets - const tag = getJSDocTagAtPosition(currentToken, position); + const tag = getJsDocTagAtPosition(currentToken, position); if (tag) { if (tag.tagName.pos <= position && position <= tag.tagName.end) { return { kind: CompletionDataKind.JsDocTagName }; } - if (isTypePositionInJSDocTag(tag, position, sourceFile)) { - insideJsDocTagTypeExpression = true; + if (isTagWithTypeExpression(tag) && tag.typeExpression && tag.typeExpression.kind === SyntaxKind.JSDocTypeExpression) { + currentToken = getTokenAtPosition(sourceFile, position); + if (!currentToken || + (!isDeclarationName(currentToken) && + (currentToken.parent.kind !== SyntaxKind.JSDocPropertyTag || + (currentToken.parent).name !== currentToken))) { + // Use as type location if inside tag's type expression + insideJsDocTagTypeExpression = isCurrentlyEditingNode(tag.typeExpression); } if (!insideJsDocTagTypeExpression && isJSDocParameterTag(tag) && (nodeIsMissing(tag.name) || tag.name.pos <= position && position <= tag.name.end)) { return { kind: CompletionDataKind.JsDocParameterName, tag }; @@ -1061,6 +1067,21 @@ namespace ts.Completions { isTypeOnlyLocation: isTypeOnly }; + type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag; + + function isTagWithTypeExpression(tag: JSDocTag): tag is JSDocTagWithTypeExpression { + switch (tag.kind) { + case SyntaxKind.JSDocParameterTag: + case SyntaxKind.JSDocPropertyTag: + case SyntaxKind.JSDocReturnTag: + case SyntaxKind.JSDocTypeTag: + case SyntaxKind.JSDocTypedefTag: + return true; + default: + return false; + } + } + function getTypeScriptMemberSymbols(): void { // Right of dot member completion list completionKind = CompletionKind.PropertyAccess; @@ -1428,6 +1449,40 @@ namespace ts.Completions { || isContextTokenTypeLocation(contextToken)); } + function isContextTokenValueLocation(contextToken: Node) { + return contextToken && + contextToken.kind === SyntaxKind.TypeOfKeyword && + (contextToken.parent.kind === SyntaxKind.TypeQuery || isTypeOfExpression(contextToken.parent)); + } + + function isContextTokenTypeLocation(contextToken: Node): boolean { + if (contextToken) { + const parentKind = contextToken.parent.kind; + switch (contextToken.kind) { + case SyntaxKind.ColonToken: + return parentKind === SyntaxKind.PropertyDeclaration || + parentKind === SyntaxKind.PropertySignature || + parentKind === SyntaxKind.Parameter || + parentKind === SyntaxKind.VariableDeclaration || + isFunctionLikeKind(parentKind); + + case SyntaxKind.EqualsToken: + return parentKind === SyntaxKind.TypeAliasDeclaration; + + case SyntaxKind.AsKeyword: + return parentKind === SyntaxKind.AsExpression; + + case SyntaxKind.LessThanToken: + return parentKind === SyntaxKind.TypeReference || + parentKind === SyntaxKind.TypeAssertionExpression; + + case SyntaxKind.ExtendsKeyword: + return parentKind === SyntaxKind.TypeParameter; + } + } + return false; + } + /** True if symbol is a type or a module containing at least one type. */ function symbolCanBeReferencedAtTypeLocation(symbol: Symbol, seenModules = createMap()): boolean { const sym = skipAlias(symbol.exportSymbol || symbol, typeChecker); @@ -2490,6 +2545,12 @@ namespace ts.Completions { return isIdentifier(node) ? node.originalKeywordKind || SyntaxKind.Unknown : node.kind; } + /** Get the corresponding JSDocTag node if the position is in a jsDoc comment */ + function getJsDocTagAtPosition(node: Node, position: number): JSDocTag | undefined { + const jsdoc = findAncestor(node, isJSDoc); + return jsdoc && jsdoc.tags && (rangeContainsPosition(jsdoc, position) ? findLast(jsdoc.tags, tag => tag.pos < position) : undefined); + } + function getPropertiesForObjectExpression(contextualType: Type, baseConstrainedType: Type | undefined, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] { const hasBaseType = baseConstrainedType && baseConstrainedType !== contextualType; const type = hasBaseType && !(baseConstrainedType!.flags & TypeFlags.AnyOrUnknown) diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 6376a873d8706..d386c3fe35baa 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -142,40 +142,6 @@ namespace ts { return name && name.parent.kind === SyntaxKind.ImportEqualsDeclaration ? SemanticMeaning.All : SemanticMeaning.Namespace; } - export function isContextTokenValueLocation(contextToken: Node) { - return contextToken && - contextToken.kind === SyntaxKind.TypeOfKeyword && - (contextToken.parent.kind === SyntaxKind.TypeQuery || isTypeOfExpression(contextToken.parent)); - } - - export function isContextTokenTypeLocation(contextToken: Node): boolean { - if (contextToken) { - const parentKind = contextToken.parent.kind; - switch (contextToken.kind) { - case SyntaxKind.ColonToken: - return parentKind === SyntaxKind.PropertyDeclaration || - parentKind === SyntaxKind.PropertySignature || - parentKind === SyntaxKind.Parameter || - parentKind === SyntaxKind.VariableDeclaration || - isFunctionLikeKind(parentKind); - - case SyntaxKind.EqualsToken: - return parentKind === SyntaxKind.TypeAliasDeclaration; - - case SyntaxKind.AsKeyword: - return parentKind === SyntaxKind.AsExpression; - - case SyntaxKind.LessThanToken: - return parentKind === SyntaxKind.TypeReference || - parentKind === SyntaxKind.TypeAssertionExpression; - - case SyntaxKind.ExtendsKeyword: - return parentKind === SyntaxKind.TypeParameter; - } - } - return false; - } - export function isInRightSideOfInternalImportEqualsDeclaration(node: Node) { while (node.parent.kind === SyntaxKind.QualifiedName) { node = node.parent; @@ -1190,41 +1156,6 @@ namespace ts { return !!findAncestor(token, isJSDoc); } - /** Get the corresponding JSDocTag node if the position is in a jsDoc comment */ - export function getJSDocTagAtPosition(node: Node, position: number): JSDocTag | undefined { - const jsdoc = findAncestor(node, isJSDoc); - return jsdoc && jsdoc.tags && (rangeContainsPosition(jsdoc, position) ? findLast(jsdoc.tags, tag => tag.pos < position) : undefined); - } - - export type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag; - - export function isTagWithTypeExpression(tag: JSDocTag): tag is JSDocTagWithTypeExpression { - switch (tag.kind) { - case SyntaxKind.JSDocParameterTag: - case SyntaxKind.JSDocPropertyTag: - case SyntaxKind.JSDocReturnTag: - case SyntaxKind.JSDocTypeTag: - case SyntaxKind.JSDocTypedefTag: - return true; - default: - return false; - } - } - - export function isTypePositionInJSDocTag(tag: JSDocTag, position: number, sourceFile: SourceFile): boolean { - if (isTagWithTypeExpression(tag) && tag.typeExpression && tag.typeExpression.kind === SyntaxKind.JSDocTypeExpression) { - const currentToken = getTokenAtPosition(sourceFile, position); - if (!currentToken || - (!isDeclarationName(currentToken) && - (currentToken.parent.kind !== SyntaxKind.JSDocPropertyTag || - (currentToken.parent).name !== currentToken))) { - // Use as type location if inside tag's type expression - return tag.typeExpression.getStart(sourceFile) <= position && position <= tag.typeExpression.getEnd(); - } - } - return false; - } - function nodeHasTokens(n: Node, sourceFile: SourceFileLike): boolean { // If we have a token or node that has a non-zero width, it must have tokens. // Note: getWidth() does not take trivia into account. From 7dc39ae97551c819589279ed9e51f6440754e797 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 27 Jan 2020 11:53:45 -0800 Subject: [PATCH 6/7] Delete unused function --- src/services/utilities.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/services/utilities.ts b/src/services/utilities.ts index d386c3fe35baa..3c6fc0430968b 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -22,21 +22,6 @@ namespace ts { All = Value | Type | Namespace } - export function getMeaningFromSymbol(symbol: Symbol, checker: TypeChecker): SemanticMeaning { - const flags = skipAlias(symbol, checker).flags; - let meaning = SemanticMeaning.None; - if (flags & SymbolFlags.Type) { - meaning |= SemanticMeaning.Type; - } - if (flags & SymbolFlags.Namespace) { - meaning |= SemanticMeaning.Namespace; - } - if (flags & SymbolFlags.Value) { - meaning |= SemanticMeaning.Value; - } - return meaning; - } - export function getMeaningFromDeclaration(node: Node): SemanticMeaning { switch (node.kind) { case SyntaxKind.VariableDeclaration: From 4aba077ef03d48991b22acbbfc78efeef094413b Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 27 Jan 2020 12:02:20 -0800 Subject: [PATCH 7/7] } --- src/services/completions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/completions.ts b/src/services/completions.ts index 98da3728de84b..8b8f1d1e140d5 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -854,6 +854,7 @@ namespace ts.Completions { (currentToken.parent).name !== currentToken))) { // Use as type location if inside tag's type expression insideJsDocTagTypeExpression = isCurrentlyEditingNode(tag.typeExpression); + } } if (!insideJsDocTagTypeExpression && isJSDocParameterTag(tag) && (nodeIsMissing(tag.name) || tag.name.pos <= position && position <= tag.name.end)) { return { kind: CompletionDataKind.JsDocParameterName, tag };