mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge branch 'master' into sourceMapGenerator
This commit is contained in:
@@ -182,6 +182,7 @@ namespace ts {
|
||||
SyntaxKind.Identifier,
|
||||
SyntaxKind.StringLiteral,
|
||||
SyntaxKind.NumericLiteral,
|
||||
SyntaxKind.BigIntLiteral,
|
||||
SyntaxKind.RegularExpressionLiteral,
|
||||
SyntaxKind.ThisKeyword,
|
||||
SyntaxKind.PlusPlusToken,
|
||||
@@ -286,6 +287,7 @@ namespace ts {
|
||||
case ClassificationType.comment: return TokenClass.Comment;
|
||||
case ClassificationType.keyword: return TokenClass.Keyword;
|
||||
case ClassificationType.numericLiteral: return TokenClass.NumberLiteral;
|
||||
case ClassificationType.bigintLiteral: return TokenClass.BigIntLiteral;
|
||||
case ClassificationType.operator: return TokenClass.Operator;
|
||||
case ClassificationType.stringLiteral: return TokenClass.StringLiteral;
|
||||
case ClassificationType.whiteSpace: return TokenClass.Whitespace;
|
||||
@@ -422,6 +424,8 @@ namespace ts {
|
||||
switch (token) {
|
||||
case SyntaxKind.NumericLiteral:
|
||||
return ClassificationType.numericLiteral;
|
||||
case SyntaxKind.BigIntLiteral:
|
||||
return ClassificationType.bigintLiteral;
|
||||
case SyntaxKind.StringLiteral:
|
||||
return ClassificationType.stringLiteral;
|
||||
case SyntaxKind.RegularExpressionLiteral:
|
||||
@@ -542,6 +546,7 @@ namespace ts {
|
||||
case ClassificationType.identifier: return ClassificationTypeNames.identifier;
|
||||
case ClassificationType.keyword: return ClassificationTypeNames.keyword;
|
||||
case ClassificationType.numericLiteral: return ClassificationTypeNames.numericLiteral;
|
||||
case ClassificationType.bigintLiteral: return ClassificationTypeNames.bigintLiteral;
|
||||
case ClassificationType.operator: return ClassificationTypeNames.operator;
|
||||
case ClassificationType.stringLiteral: return ClassificationTypeNames.stringLiteral;
|
||||
case ClassificationType.whiteSpace: return ClassificationTypeNames.whiteSpace;
|
||||
@@ -698,7 +703,7 @@ namespace ts {
|
||||
pushCommentRange(pos, tag.pos - pos);
|
||||
}
|
||||
|
||||
pushClassification(tag.atToken.pos, tag.atToken.end - tag.atToken.pos, ClassificationType.punctuation); // "@"
|
||||
pushClassification(tag.pos, 1, ClassificationType.punctuation); // "@"
|
||||
pushClassification(tag.tagName.pos, tag.tagName.end - tag.tagName.pos, ClassificationType.docCommentTagName); // e.g. "param"
|
||||
|
||||
pos = tag.tagName.end;
|
||||
@@ -886,6 +891,9 @@ namespace ts {
|
||||
else if (tokenKind === SyntaxKind.NumericLiteral) {
|
||||
return ClassificationType.numericLiteral;
|
||||
}
|
||||
else if (tokenKind === SyntaxKind.BigIntLiteral) {
|
||||
return ClassificationType.bigintLiteral;
|
||||
}
|
||||
else if (tokenKind === SyntaxKind.StringLiteral) {
|
||||
// TODO: GH#18217
|
||||
return token!.parent.kind === SyntaxKind.JsxAttribute ? ClassificationType.jsxAttributeStringLiteralValue : ClassificationType.stringLiteral;
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace ts {
|
||||
return arrayFrom(errorCodeToFixes.keys());
|
||||
}
|
||||
|
||||
export function getFixes(context: CodeFixContext): CodeFixAction[] {
|
||||
export function getFixes(context: CodeFixContext): ReadonlyArray<CodeFixAction> {
|
||||
return flatMap(errorCodeToFixes.get(String(context.errorCode)) || emptyArray, f => f.getCodeActions(context));
|
||||
}
|
||||
|
||||
|
||||
@@ -58,9 +58,9 @@ namespace ts.codefix {
|
||||
const allVarNames: SymbolAndIdentifier[] = [];
|
||||
const isInJavascript = isInJSFile(functionToConvert);
|
||||
const setOfExpressionsToReturn = getAllPromiseExpressionsToReturn(functionToConvert, checker);
|
||||
const functionToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context, setOfExpressionsToReturn, originalTypeMap, allVarNames);
|
||||
const functionToConvertRenamed = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context, setOfExpressionsToReturn, originalTypeMap, allVarNames);
|
||||
const constIdentifiers = getConstIdentifiers(synthNamesMap);
|
||||
const returnStatements = getReturnStatementsWithPromiseHandlers(functionToConvertRenamed);
|
||||
const returnStatements = functionToConvertRenamed.body && isBlock(functionToConvertRenamed.body) ? getReturnStatementsWithPromiseHandlers(functionToConvertRenamed.body) : emptyArray;
|
||||
const transformer: Transformer = { checker, synthNamesMap, allVarNames, setOfExpressionsToReturn, constIdentifiers, originalTypeMap, isInJSFile: isInJavascript };
|
||||
|
||||
if (!returnStatements.length) {
|
||||
@@ -87,6 +87,14 @@ namespace ts.codefix {
|
||||
}
|
||||
}
|
||||
|
||||
function getReturnStatementsWithPromiseHandlers(body: Block): ReadonlyArray<ReturnStatement> {
|
||||
const res: ReturnStatement[] = [];
|
||||
forEachReturnStatement(body, ret => {
|
||||
if (isReturnStatementWithFixablePromiseHandler(ret)) res.push(ret);
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
// Returns the identifiers that are never reassigned in the refactor
|
||||
function getConstIdentifiers(synthNamesMap: ReadonlyMap<SynthIdentifier>): Identifier[] {
|
||||
const constIdentifiers: Identifier[] = [];
|
||||
@@ -442,7 +450,7 @@ namespace ts.codefix {
|
||||
seenReturnStatement = true;
|
||||
}
|
||||
|
||||
if (getReturnStatementsWithPromiseHandlers(statement).length) {
|
||||
if (isReturnStatementWithFixablePromiseHandler(statement)) {
|
||||
refactoredStmts = refactoredStmts.concat(getInnerTransformationBody(transformer, [statement], prevArgName));
|
||||
}
|
||||
else {
|
||||
@@ -458,7 +466,7 @@ namespace ts.codefix {
|
||||
seenReturnStatement);
|
||||
}
|
||||
else {
|
||||
const innerRetStmts = getReturnStatementsWithPromiseHandlers(createReturn(funcBody));
|
||||
const innerRetStmts = isFixablePromiseHandler(funcBody) ? [createReturn(funcBody)] : emptyArray;
|
||||
const innerCbBody = getInnerTransformationBody(transformer, innerRetStmts, prevArgName);
|
||||
|
||||
if (innerCbBody.length > 0) {
|
||||
|
||||
@@ -185,6 +185,10 @@ namespace ts.codefix {
|
||||
const widenedType = checker.getWidenedType(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(otherExpression)));
|
||||
typeNode = checker.typeToTypeNode(widenedType, classDeclaration);
|
||||
}
|
||||
else {
|
||||
const contextualType = checker.getContextualType(token.parent as Expression);
|
||||
typeNode = contextualType ? checker.typeToTypeNode(contextualType) : undefined;
|
||||
}
|
||||
return typeNode || createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
||||
}
|
||||
|
||||
|
||||
@@ -181,8 +181,8 @@ namespace ts.codefix {
|
||||
|
||||
function deleteAssignments(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Identifier, checker: TypeChecker) {
|
||||
FindAllReferences.Core.eachSymbolReferenceInFile(token, checker, sourceFile, (ref: Node) => {
|
||||
if (ref.parent.kind === SyntaxKind.PropertyAccessExpression) ref = ref.parent;
|
||||
if (ref.parent.kind === SyntaxKind.BinaryExpression && ref.parent.parent.kind === SyntaxKind.ExpressionStatement) {
|
||||
if (isPropertyAccessExpression(ref.parent) && ref.parent.name === ref) ref = ref.parent;
|
||||
if (isBinaryExpression(ref.parent) && isExpressionStatement(ref.parent.parent) && ref.parent.left === ref) {
|
||||
changes.delete(sourceFile, ref.parent.parent);
|
||||
}
|
||||
});
|
||||
@@ -200,8 +200,16 @@ namespace ts.codefix {
|
||||
|
||||
function tryDeleteParameter(changes: textChanges.ChangeTracker, sourceFile: SourceFile, p: ParameterDeclaration, checker: TypeChecker, sourceFiles: ReadonlyArray<SourceFile>, isFixAll: boolean): void {
|
||||
if (mayDeleteParameter(p, checker, isFixAll)) {
|
||||
changes.delete(sourceFile, p);
|
||||
deleteUnusedArguments(changes, sourceFile, p, sourceFiles, checker);
|
||||
if (p.modifiers && p.modifiers.length > 0
|
||||
&& (!isIdentifier(p.name) || FindAllReferences.Core.isSymbolReferencedInFile(p.name, checker, sourceFile))) {
|
||||
p.modifiers.forEach(modifier => {
|
||||
changes.deleteModifier(sourceFile, modifier);
|
||||
});
|
||||
}
|
||||
else {
|
||||
changes.delete(sourceFile, p);
|
||||
deleteUnusedArguments(changes, sourceFile, p, sourceFiles, checker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
/* @internal */
|
||||
namespace ts {
|
||||
export function generateTypesForModule(name: string, moduleValue: unknown, formatSettings: FormatCodeSettings): string {
|
||||
return valueInfoToDeclarationFileText(inspectValue(name, moduleValue), formatSettings);
|
||||
return generateTypesForModuleOrGlobal(name, moduleValue, formatSettings, OutputKind.ExportEquals);
|
||||
}
|
||||
|
||||
export function valueInfoToDeclarationFileText(valueInfo: ValueInfo, formatSettings: FormatCodeSettings): string {
|
||||
return textChanges.getNewFileText(toStatements(valueInfo, OutputKind.ExportEquals), ScriptKind.TS, "\n", formatting.getFormatContext(formatSettings));
|
||||
export function generateTypesForGlobal(name: string, globalValue: unknown, formatSettings: FormatCodeSettings): string {
|
||||
return generateTypesForModuleOrGlobal(name, globalValue, formatSettings, OutputKind.Global);
|
||||
}
|
||||
|
||||
const enum OutputKind { ExportEquals, NamedExport, NamespaceMember }
|
||||
function generateTypesForModuleOrGlobal(name: string, globalValue: unknown, formatSettings: FormatCodeSettings, outputKind: OutputKind.ExportEquals | OutputKind.Global): string {
|
||||
return valueInfoToDeclarationFileText(inspectValue(name, globalValue), formatSettings, outputKind);
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function valueInfoToDeclarationFileText(valueInfo: ValueInfo, formatSettings: FormatCodeSettings, outputKind: OutputKind.ExportEquals | OutputKind.Global = OutputKind.ExportEquals): string {
|
||||
return textChanges.getNewFileText(toStatements(valueInfo, outputKind), ScriptKind.TS, formatSettings.newLineCharacter || "\n", formatting.getFormatContext(formatSettings));
|
||||
}
|
||||
|
||||
const enum OutputKind { ExportEquals, NamedExport, NamespaceMember, Global }
|
||||
function toNamespaceMemberStatements(info: ValueInfo): ReadonlyArray<Statement> {
|
||||
return toStatements(info, OutputKind.NamespaceMember);
|
||||
}
|
||||
@@ -18,7 +26,7 @@ namespace ts {
|
||||
if (!isValidIdentifier(name) || isDefault && kind !== OutputKind.NamedExport) return emptyArray;
|
||||
|
||||
const modifiers = isDefault && info.kind === ValueKind.FunctionOrClass ? [createModifier(SyntaxKind.ExportKeyword), createModifier(SyntaxKind.DefaultKeyword)]
|
||||
: kind === OutputKind.ExportEquals ? [createModifier(SyntaxKind.DeclareKeyword)]
|
||||
: kind === OutputKind.Global || kind === OutputKind.ExportEquals ? [createModifier(SyntaxKind.DeclareKeyword)]
|
||||
: kind === OutputKind.NamedExport ? [createModifier(SyntaxKind.ExportKeyword)]
|
||||
: undefined;
|
||||
const exportEquals = () => kind === OutputKind.ExportEquals ? [exportEqualsOrDefault(info.name, /*isExportEquals*/ true)] : emptyArray;
|
||||
@@ -132,11 +140,14 @@ namespace ts {
|
||||
case ValueKind.FunctionOrClass:
|
||||
return createTypeReferenceNode("Function", /*typeArguments*/ undefined); // Normally we create a FunctionDeclaration, but this can happen for a function in an array.
|
||||
case ValueKind.Object:
|
||||
return createTypeLiteralNode(info.members.map(m => createPropertySignature(/*modifiers*/ undefined, m.name, /*questionToken*/ undefined, toType(m), /*initializer*/ undefined)));
|
||||
return createTypeLiteralNode(info.members.map(m => createPropertySignature(/*modifiers*/ undefined, toPropertyName(m.name), /*questionToken*/ undefined, toType(m), /*initializer*/ undefined)));
|
||||
default:
|
||||
return Debug.assertNever(info);
|
||||
}
|
||||
}
|
||||
function toPropertyName(name: string): Identifier | StringLiteral {
|
||||
return isIdentifierText(name, ScriptTarget.ESNext) ? createIdentifier(name) : createStringLiteral(name);
|
||||
}
|
||||
|
||||
// Parses assignments to "this.x" in the constructor into class property declarations
|
||||
function getConstructorFunctionInstanceProperties(fnAst: FunctionOrConstructorNode): ReadonlyArray<PropertyDeclaration> {
|
||||
|
||||
@@ -185,20 +185,20 @@ namespace ts.codefix {
|
||||
|
||||
const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, compilerOptions);
|
||||
if (defaultInfo && defaultInfo.name === symbolName && skipAlias(defaultInfo.symbol, checker) === exportedSymbol) {
|
||||
result.push({ moduleSymbol, importKind: defaultInfo.kind, exportedSymbolIsTypeOnly: isTypeOnlySymbol(defaultInfo.symbol) });
|
||||
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, exportedSymbolIsTypeOnly: isTypeOnlySymbol(exported) });
|
||||
result.push({ moduleSymbol, importKind: ImportKind.Named, exportedSymbolIsTypeOnly: isTypeOnlySymbol(exported, checker) });
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function isTypeOnlySymbol(s: Symbol): boolean {
|
||||
return !(s.flags & SymbolFlags.Value);
|
||||
function isTypeOnlySymbol(s: Symbol, checker: TypeChecker): boolean {
|
||||
return !(skipAlias(s, checker).flags & SymbolFlags.Value);
|
||||
}
|
||||
|
||||
function getFixForImport(
|
||||
@@ -289,7 +289,7 @@ namespace ts.codefix {
|
||||
// `position` should only be undefined at a missing jsx namespace, in which case we shouldn't be looking for pure types.
|
||||
exportedSymbolIsTypeOnly && isJs ? { kind: ImportFixKind.ImportType, moduleSpecifier, position: Debug.assertDefined(position) } : { kind: ImportFixKind.AddNew, moduleSpecifier, importKind }));
|
||||
// Sort to keep the shortest paths first
|
||||
return choicesForEachExportingModule.sort((a, b) => a.moduleSpecifier.length - b.moduleSpecifier.length);
|
||||
return sort(choicesForEachExportingModule, (a, b) => a.moduleSpecifier.length - b.moduleSpecifier.length);
|
||||
}
|
||||
|
||||
function getFixesForAddImport(
|
||||
@@ -398,7 +398,7 @@ namespace ts.codefix {
|
||||
// Maps symbol id to info for modules providing that symbol (original export + re-exports).
|
||||
const originalSymbolToExportInfos = createMultiMap<SymbolExportInfo>();
|
||||
function addSymbol(moduleSymbol: Symbol, exportedSymbol: Symbol, importKind: ImportKind): void {
|
||||
originalSymbolToExportInfos.add(getUniqueSymbolId(exportedSymbol, checker).toString(), { moduleSymbol, importKind, exportedSymbolIsTypeOnly: isTypeOnlySymbol(exportedSymbol) });
|
||||
originalSymbolToExportInfos.add(getUniqueSymbolId(exportedSymbol, checker).toString(), { moduleSymbol, importKind, exportedSymbolIsTypeOnly: isTypeOnlySymbol(exportedSymbol, checker) });
|
||||
}
|
||||
forEachExternalModuleToImportFrom(checker, sourceFile, program.getSourceFiles(), moduleSymbol => {
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
@@ -424,7 +424,7 @@ namespace ts.codefix {
|
||||
if (!exported) return undefined;
|
||||
const { symbol, kind } = exported;
|
||||
const info = getDefaultExportInfoWorker(symbol, moduleSymbol, checker, compilerOptions);
|
||||
return info && { symbol, symbolForMeaning: info.symbolForMeaning, name: info.name, kind };
|
||||
return info && { symbol, kind, ...info };
|
||||
}
|
||||
|
||||
function getDefaultLikeExportWorker(moduleSymbol: Symbol, checker: TypeChecker): { readonly symbol: Symbol, readonly kind: ImportKind.Default | ImportKind.Equals } | undefined {
|
||||
|
||||
@@ -21,28 +21,46 @@ namespace ts.codefix {
|
||||
|
||||
// Property declarations
|
||||
Diagnostics.Member_0_implicitly_has_an_1_type.code,
|
||||
|
||||
//// Suggestions
|
||||
// Variable declarations
|
||||
Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_but_a_better_type_may_be_inferred_from_usage.code,
|
||||
|
||||
// Variable uses
|
||||
Diagnostics.Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code,
|
||||
|
||||
// Parameter declarations
|
||||
Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code,
|
||||
Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage.code,
|
||||
|
||||
// Get Accessor declarations
|
||||
Diagnostics.Property_0_implicitly_has_type_any_but_a_better_type_for_its_get_accessor_may_be_inferred_from_usage.code,
|
||||
Diagnostics._0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage.code,
|
||||
|
||||
// Set Accessor declarations
|
||||
Diagnostics.Property_0_implicitly_has_type_any_but_a_better_type_for_its_set_accessor_may_be_inferred_from_usage.code,
|
||||
|
||||
// Property declarations
|
||||
Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code,
|
||||
];
|
||||
registerCodeFix({
|
||||
errorCodes,
|
||||
getCodeActions(context) {
|
||||
const { sourceFile, program, span: { start }, errorCode, cancellationToken } = context;
|
||||
if (isSourceFileJS(sourceFile)) {
|
||||
return undefined; // TODO: GH#20113
|
||||
}
|
||||
const { sourceFile, program, span: { start }, errorCode, cancellationToken, host } = context;
|
||||
|
||||
const token = getTokenAtPosition(sourceFile, start);
|
||||
let declaration!: Declaration | undefined;
|
||||
const changes = textChanges.ChangeTracker.with(context, changes => { declaration = doChange(changes, sourceFile, token, errorCode, program, cancellationToken, /*markSeenseen*/ returnTrue); });
|
||||
const changes = textChanges.ChangeTracker.with(context, changes => { declaration = doChange(changes, sourceFile, token, errorCode, program, cancellationToken, /*markSeen*/ returnTrue, host); });
|
||||
const name = declaration && getNameOfDeclaration(declaration);
|
||||
return !name || changes.length === 0 ? undefined
|
||||
: [createCodeFixAction(fixId, changes, [getDiagnostic(errorCode, token), name.getText(sourceFile)], fixId, Diagnostics.Infer_all_types_from_usage)];
|
||||
},
|
||||
fixIds: [fixId],
|
||||
getAllCodeActions(context) {
|
||||
const { sourceFile, program, cancellationToken } = context;
|
||||
const { sourceFile, program, cancellationToken, host } = context;
|
||||
const markSeen = nodeSeenTracker();
|
||||
return codeFixAll(context, errorCodes, (changes, err) => {
|
||||
doChange(changes, sourceFile, getTokenAtPosition(err.file, err.start), err.code, program, cancellationToken, markSeen);
|
||||
doChange(changes, sourceFile, getTokenAtPosition(err.file, err.start), err.code, program, cancellationToken, markSeen, host);
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -50,26 +68,62 @@ namespace ts.codefix {
|
||||
function getDiagnostic(errorCode: number, token: Node): DiagnosticMessage {
|
||||
switch (errorCode) {
|
||||
case Diagnostics.Parameter_0_implicitly_has_an_1_type.code:
|
||||
return isSetAccessor(getContainingFunction(token)!) ? Diagnostics.Infer_type_of_0_from_usage : Diagnostics.Infer_parameter_types_from_usage; // TODO: GH#18217
|
||||
case Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code:
|
||||
return isSetAccessorDeclaration(getContainingFunction(token)!) ? Diagnostics.Infer_type_of_0_from_usage : Diagnostics.Infer_parameter_types_from_usage; // TODO: GH#18217
|
||||
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code:
|
||||
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage.code:
|
||||
return Diagnostics.Infer_parameter_types_from_usage;
|
||||
default:
|
||||
return Diagnostics.Infer_type_of_0_from_usage;
|
||||
}
|
||||
}
|
||||
|
||||
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node, errorCode: number, program: Program, cancellationToken: CancellationToken, markSeen: NodeSeenTracker): Declaration | undefined {
|
||||
if (!isParameterPropertyModifier(token.kind) && token.kind !== SyntaxKind.Identifier && token.kind !== SyntaxKind.DotDotDotToken) {
|
||||
/** Map suggestion code to error code */
|
||||
function mapSuggestionDiagnostic(errorCode: number) {
|
||||
switch (errorCode) {
|
||||
case Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_but_a_better_type_may_be_inferred_from_usage.code:
|
||||
return Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code;
|
||||
case Diagnostics.Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code:
|
||||
return Diagnostics.Variable_0_implicitly_has_an_1_type.code;
|
||||
case Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code:
|
||||
return Diagnostics.Parameter_0_implicitly_has_an_1_type.code;
|
||||
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage.code:
|
||||
return Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code;
|
||||
case Diagnostics.Property_0_implicitly_has_type_any_but_a_better_type_for_its_get_accessor_may_be_inferred_from_usage.code:
|
||||
return Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code;
|
||||
case Diagnostics._0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage.code:
|
||||
return Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code;
|
||||
case Diagnostics.Property_0_implicitly_has_type_any_but_a_better_type_for_its_set_accessor_may_be_inferred_from_usage.code:
|
||||
return Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code;
|
||||
case Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code:
|
||||
return Diagnostics.Member_0_implicitly_has_an_1_type.code;
|
||||
}
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node, errorCode: number, program: Program, cancellationToken: CancellationToken, markSeen: NodeSeenTracker, host: LanguageServiceHost): Declaration | undefined {
|
||||
if (!isParameterPropertyModifier(token.kind) && token.kind !== SyntaxKind.Identifier && token.kind !== SyntaxKind.DotDotDotToken && token.kind !== SyntaxKind.ThisKeyword) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { parent } = token;
|
||||
errorCode = mapSuggestionDiagnostic(errorCode);
|
||||
switch (errorCode) {
|
||||
// Variable and Property declarations
|
||||
case Diagnostics.Member_0_implicitly_has_an_1_type.code:
|
||||
case Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code:
|
||||
if ((isVariableDeclaration(parent) && markSeen(parent)) || isPropertyDeclaration(parent) || isPropertySignature(parent)) { // handle bad location
|
||||
annotateVariableDeclaration(changes, sourceFile, parent, program, cancellationToken);
|
||||
annotateVariableDeclaration(changes, sourceFile, parent, program, host, cancellationToken);
|
||||
return parent;
|
||||
}
|
||||
if (isPropertyAccessExpression(parent)) {
|
||||
const type = inferTypeForVariableFromUsage(parent.name, program, cancellationToken);
|
||||
const typeNode = getTypeNodeIfAccessible(type, parent, program, host);
|
||||
if (typeNode) {
|
||||
// Note that the codefix will never fire with an existing `@type` tag, so there is no need to merge tags
|
||||
const typeTag = createJSDocTypeTag(createJSDocTypeExpression(typeNode), /*comment*/ "");
|
||||
addJSDocTags(changes, sourceFile, cast(parent.parent.parent, isExpressionStatement), [typeTag]);
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
return undefined;
|
||||
@@ -77,7 +131,7 @@ namespace ts.codefix {
|
||||
case Diagnostics.Variable_0_implicitly_has_an_1_type.code: {
|
||||
const symbol = program.getTypeChecker().getSymbolAtLocation(token);
|
||||
if (symbol && symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) && markSeen(symbol.valueDeclaration)) {
|
||||
annotateVariableDeclaration(changes, sourceFile, symbol.valueDeclaration, program, cancellationToken);
|
||||
annotateVariableDeclaration(changes, sourceFile, symbol.valueDeclaration, program, host, cancellationToken);
|
||||
return symbol.valueDeclaration;
|
||||
}
|
||||
return undefined;
|
||||
@@ -92,15 +146,15 @@ namespace ts.codefix {
|
||||
switch (errorCode) {
|
||||
// Parameter declarations
|
||||
case Diagnostics.Parameter_0_implicitly_has_an_1_type.code:
|
||||
if (isSetAccessor(containingFunction)) {
|
||||
annotateSetAccessor(changes, sourceFile, containingFunction, program, cancellationToken);
|
||||
if (isSetAccessorDeclaration(containingFunction)) {
|
||||
annotateSetAccessor(changes, sourceFile, containingFunction, program, host, cancellationToken);
|
||||
return containingFunction;
|
||||
}
|
||||
// falls through
|
||||
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code:
|
||||
if (markSeen(containingFunction)) {
|
||||
const param = cast(parent, isParameter);
|
||||
annotateParameters(changes, param, containingFunction, sourceFile, program, cancellationToken);
|
||||
annotateParameters(changes, sourceFile, param, containingFunction, program, host, cancellationToken);
|
||||
return param;
|
||||
}
|
||||
return undefined;
|
||||
@@ -108,16 +162,16 @@ namespace ts.codefix {
|
||||
// Get Accessor declarations
|
||||
case Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code:
|
||||
case Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code:
|
||||
if (isGetAccessor(containingFunction) && isIdentifier(containingFunction.name)) {
|
||||
annotate(changes, sourceFile, containingFunction, inferTypeForVariableFromUsage(containingFunction.name, program, cancellationToken), program);
|
||||
if (isGetAccessorDeclaration(containingFunction) && isIdentifier(containingFunction.name)) {
|
||||
annotate(changes, sourceFile, containingFunction, inferTypeForVariableFromUsage(containingFunction.name, program, cancellationToken), program, host);
|
||||
return containingFunction;
|
||||
}
|
||||
return undefined;
|
||||
|
||||
// Set Accessor declarations
|
||||
case Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code:
|
||||
if (isSetAccessor(containingFunction)) {
|
||||
annotateSetAccessor(changes, sourceFile, containingFunction, program, cancellationToken);
|
||||
if (isSetAccessorDeclaration(containingFunction)) {
|
||||
annotateSetAccessor(changes, sourceFile, containingFunction, program, host, cancellationToken);
|
||||
return containingFunction;
|
||||
}
|
||||
return undefined;
|
||||
@@ -127,9 +181,9 @@ namespace ts.codefix {
|
||||
}
|
||||
}
|
||||
|
||||
function annotateVariableDeclaration(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: VariableDeclaration | PropertyDeclaration | PropertySignature, program: Program, cancellationToken: CancellationToken): void {
|
||||
function annotateVariableDeclaration(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: VariableDeclaration | PropertyDeclaration | PropertySignature, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken): void {
|
||||
if (isIdentifier(declaration.name)) {
|
||||
annotate(changes, sourceFile, declaration, inferTypeForVariableFromUsage(declaration.name, program, cancellationToken), program);
|
||||
annotate(changes, sourceFile, declaration, inferTypeForVariableFromUsage(declaration.name, program, cancellationToken), program, host);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,45 +194,119 @@ namespace ts.codefix {
|
||||
case SyntaxKind.Constructor:
|
||||
return true;
|
||||
case SyntaxKind.FunctionExpression:
|
||||
return !!declaration.name;
|
||||
const parent = declaration.parent;
|
||||
return isVariableDeclaration(parent) && isIdentifier(parent.name) || !!declaration.name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function annotateParameters(changes: textChanges.ChangeTracker, parameterDeclaration: ParameterDeclaration, containingFunction: FunctionLike, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): void {
|
||||
function annotateParameters(changes: textChanges.ChangeTracker, sourceFile: SourceFile, parameterDeclaration: ParameterDeclaration, containingFunction: FunctionLike, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken): void {
|
||||
if (!isIdentifier(parameterDeclaration.name) || !isApplicableFunctionForInference(containingFunction)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const types = inferTypeForParametersFromUsage(containingFunction, sourceFile, program, cancellationToken) ||
|
||||
containingFunction.parameters.map(p => isIdentifier(p.name) ? inferTypeForVariableFromUsage(p.name, program, cancellationToken) : undefined);
|
||||
// We didn't actually find a set of type inference positions matching each parameter position
|
||||
if (!types || containingFunction.parameters.length !== types.length) {
|
||||
return;
|
||||
}
|
||||
const parameterInferences = inferTypeForParametersFromUsage(containingFunction, sourceFile, program, cancellationToken) ||
|
||||
containingFunction.parameters.map<ParameterInference>(p => ({
|
||||
declaration: p,
|
||||
type: isIdentifier(p.name) ? inferTypeForVariableFromUsage(p.name, program, cancellationToken) : program.getTypeChecker().getAnyType()
|
||||
}));
|
||||
Debug.assert(containingFunction.parameters.length === parameterInferences.length);
|
||||
|
||||
zipWith(containingFunction.parameters, types, (parameter, type) => {
|
||||
if (!parameter.type && !parameter.initializer) {
|
||||
annotate(changes, sourceFile, parameter, type, program);
|
||||
if (isInJSFile(containingFunction)) {
|
||||
annotateJSDocParameters(changes, sourceFile, parameterInferences, program, host);
|
||||
}
|
||||
else {
|
||||
for (const { declaration, type } of parameterInferences) {
|
||||
if (declaration && !declaration.type && !declaration.initializer) {
|
||||
annotate(changes, sourceFile, declaration, type, program, host);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function annotateSetAccessor(changes: textChanges.ChangeTracker, sourceFile: SourceFile, setAccessorDeclaration: SetAccessorDeclaration, program: Program, cancellationToken: CancellationToken): void {
|
||||
function annotateSetAccessor(changes: textChanges.ChangeTracker, sourceFile: SourceFile, setAccessorDeclaration: SetAccessorDeclaration, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken): void {
|
||||
const param = firstOrUndefined(setAccessorDeclaration.parameters);
|
||||
if (param && isIdentifier(setAccessorDeclaration.name) && isIdentifier(param.name)) {
|
||||
const type = inferTypeForVariableFromUsage(setAccessorDeclaration.name, program, cancellationToken) ||
|
||||
inferTypeForVariableFromUsage(param.name, program, cancellationToken);
|
||||
annotate(changes, sourceFile, param, type, program);
|
||||
let type = inferTypeForVariableFromUsage(setAccessorDeclaration.name, program, cancellationToken);
|
||||
if (type === program.getTypeChecker().getAnyType()) {
|
||||
type = inferTypeForVariableFromUsage(param.name, program, cancellationToken);
|
||||
}
|
||||
if (isInJSFile(setAccessorDeclaration)) {
|
||||
annotateJSDocParameters(changes, sourceFile, [{ declaration: param, type }], program, host);
|
||||
}
|
||||
else {
|
||||
annotate(changes, sourceFile, param, type, program, host);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function annotate(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type | undefined, program: Program): void {
|
||||
const typeNode = type && getTypeNodeIfAccessible(type, declaration, program.getTypeChecker());
|
||||
if (typeNode) changes.tryInsertTypeAnnotation(sourceFile, declaration, typeNode);
|
||||
function annotate(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type, program: Program, host: LanguageServiceHost): void {
|
||||
const typeNode = getTypeNodeIfAccessible(type, declaration, program, host);
|
||||
if (typeNode) {
|
||||
if (isInJSFile(sourceFile) && declaration.kind !== SyntaxKind.PropertySignature) {
|
||||
const parent = isVariableDeclaration(declaration) ? tryCast(declaration.parent.parent, isVariableStatement) : declaration;
|
||||
if (!parent) {
|
||||
return;
|
||||
}
|
||||
const typeExpression = createJSDocTypeExpression(typeNode);
|
||||
const typeTag = isGetAccessorDeclaration(declaration) ? createJSDocReturnTag(typeExpression, "") : createJSDocTypeTag(typeExpression, "");
|
||||
addJSDocTags(changes, sourceFile, parent, [typeTag]);
|
||||
}
|
||||
else {
|
||||
changes.tryInsertTypeAnnotation(sourceFile, declaration, typeNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, checker: TypeChecker): TypeNode | undefined {
|
||||
function annotateJSDocParameters(changes: textChanges.ChangeTracker, sourceFile: SourceFile, parameterInferences: ReadonlyArray<ParameterInference>, program: Program, host: LanguageServiceHost): void {
|
||||
const signature = parameterInferences.length && parameterInferences[0].declaration.parent;
|
||||
if (!signature) {
|
||||
return;
|
||||
}
|
||||
const paramTags = mapDefined(parameterInferences, inference => {
|
||||
const param = inference.declaration;
|
||||
// only infer parameters that have (1) no type and (2) an accessible inferred type
|
||||
if (param.initializer || getJSDocType(param) || !isIdentifier(param.name)) return;
|
||||
|
||||
const typeNode = inference.type && getTypeNodeIfAccessible(inference.type, param, program, host);
|
||||
const name = getSynthesizedClone(param.name);
|
||||
setEmitFlags(name, EmitFlags.NoComments | EmitFlags.NoNestedComments);
|
||||
return typeNode && createJSDocParamTag(name, !!inference.isOptional, createJSDocTypeExpression(typeNode), "");
|
||||
});
|
||||
addJSDocTags(changes, sourceFile, signature, paramTags);
|
||||
}
|
||||
|
||||
function addJSDocTags(changes: textChanges.ChangeTracker, sourceFile: SourceFile, parent: HasJSDoc, newTags: ReadonlyArray<JSDocTag>): void {
|
||||
const comments = mapDefined(parent.jsDoc, j => j.comment);
|
||||
const oldTags = flatMapToMutable(parent.jsDoc, j => j.tags);
|
||||
const unmergedNewTags = newTags.filter(newTag => !oldTags || !oldTags.some((tag, i) => {
|
||||
const merged = tryMergeJsdocTags(tag, newTag);
|
||||
if (merged) oldTags[i] = merged;
|
||||
return !!merged;
|
||||
}));
|
||||
const tag = createJSDocComment(comments.join("\n"), createNodeArray([...(oldTags || emptyArray), ...unmergedNewTags]));
|
||||
changes.insertJsdocCommentBefore(sourceFile, parent, tag);
|
||||
}
|
||||
|
||||
function tryMergeJsdocTags(oldTag: JSDocTag, newTag: JSDocTag): JSDocTag | undefined {
|
||||
if (oldTag.kind !== newTag.kind) {
|
||||
return undefined;
|
||||
}
|
||||
switch (oldTag.kind) {
|
||||
case SyntaxKind.JSDocParameterTag: {
|
||||
const oldParam = oldTag as JSDocParameterTag;
|
||||
const newParam = newTag as JSDocParameterTag;
|
||||
return isIdentifier(oldParam.name) && isIdentifier(newParam.name) && oldParam.name.escapedText === newParam.name.escapedText
|
||||
? createJSDocParamTag(newParam.name, newParam.isBracketed, newParam.typeExpression, oldParam.comment)
|
||||
: undefined;
|
||||
}
|
||||
case SyntaxKind.JSDocReturnTag:
|
||||
return createJSDocReturnTag((newTag as JSDocReturnTag).typeExpression, oldTag.comment);
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, program: Program, host: LanguageServiceHost): TypeNode | undefined {
|
||||
const checker = program.getTypeChecker();
|
||||
let typeIsAccessible = true;
|
||||
const notAccessible = () => { typeIsAccessible = false; };
|
||||
const res = checker.typeToTypeNode(type, enclosingScope, /*flags*/ undefined, {
|
||||
@@ -189,6 +317,14 @@ namespace ts.codefix {
|
||||
reportInaccessibleThisError: notAccessible,
|
||||
reportPrivateInBaseOfClassExpression: notAccessible,
|
||||
reportInaccessibleUniqueSymbolError: notAccessible,
|
||||
moduleResolverHost: {
|
||||
readFile: host.readFile,
|
||||
fileExists: host.fileExists,
|
||||
directoryExists: host.directoryExists,
|
||||
getSourceFiles: program.getSourceFiles,
|
||||
getCurrentDirectory: program.getCurrentDirectory,
|
||||
getCommonSourceDirectory: program.getCommonSourceDirectory,
|
||||
}
|
||||
});
|
||||
return typeIsAccessible ? res : undefined;
|
||||
}
|
||||
@@ -199,24 +335,39 @@ namespace ts.codefix {
|
||||
entry.kind !== FindAllReferences.EntryKind.Span ? tryCast(entry.node, isIdentifier) : undefined);
|
||||
}
|
||||
|
||||
function inferTypeForVariableFromUsage(token: Identifier, program: Program, cancellationToken: CancellationToken): Type | undefined {
|
||||
return InferFromReference.inferTypeFromReferences(getReferences(token, program, cancellationToken), program.getTypeChecker(), cancellationToken);
|
||||
function inferTypeForVariableFromUsage(token: Identifier, program: Program, cancellationToken: CancellationToken): Type {
|
||||
const references = getReferences(token, program, cancellationToken);
|
||||
const checker = program.getTypeChecker();
|
||||
const types = InferFromReference.inferTypesFromReferences(references, checker, cancellationToken);
|
||||
return InferFromReference.unifyFromContext(types, checker);
|
||||
}
|
||||
|
||||
function inferTypeForParametersFromUsage(containingFunction: FunctionLikeDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): (Type | undefined)[] | undefined {
|
||||
function inferTypeForParametersFromUsage(containingFunction: FunctionLikeDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): ParameterInference[] | undefined {
|
||||
let searchToken;
|
||||
switch (containingFunction.kind) {
|
||||
case SyntaxKind.Constructor:
|
||||
searchToken = findChildOfKind<Token<SyntaxKind.ConstructorKeyword>>(containingFunction, SyntaxKind.ConstructorKeyword, sourceFile);
|
||||
break;
|
||||
case SyntaxKind.FunctionExpression:
|
||||
const parent = containingFunction.parent;
|
||||
searchToken = isVariableDeclaration(parent) && isIdentifier(parent.name) ?
|
||||
parent.name :
|
||||
containingFunction.name;
|
||||
break;
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
const isConstructor = containingFunction.kind === SyntaxKind.Constructor;
|
||||
const searchToken = isConstructor ?
|
||||
findChildOfKind<Token<SyntaxKind.ConstructorKeyword>>(containingFunction, SyntaxKind.ConstructorKeyword, sourceFile) :
|
||||
containingFunction.name;
|
||||
if (searchToken) {
|
||||
return InferFromReference.inferTypeForParametersFromReferences(getReferences(searchToken, program, cancellationToken), containingFunction, program.getTypeChecker(), cancellationToken);
|
||||
}
|
||||
searchToken = containingFunction.name;
|
||||
break;
|
||||
}
|
||||
if (searchToken) {
|
||||
return InferFromReference.inferTypeForParametersFromReferences(getReferences(searchToken, program, cancellationToken), containingFunction, program, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
interface ParameterInference {
|
||||
readonly declaration: ParameterDeclaration;
|
||||
readonly type: Type;
|
||||
readonly isOptional?: boolean;
|
||||
}
|
||||
|
||||
namespace InferFromReference {
|
||||
@@ -228,7 +379,9 @@ namespace ts.codefix {
|
||||
interface UsageContext {
|
||||
isNumber?: boolean;
|
||||
isString?: boolean;
|
||||
isNumberOrString?: boolean;
|
||||
hasNonVacuousType?: boolean;
|
||||
hasNonVacuousNonAnonymousType?: boolean;
|
||||
|
||||
candidateTypes?: Type[];
|
||||
properties?: UnderscoreEscapedMap<UsageContext>;
|
||||
callContexts?: CallContext[];
|
||||
@@ -237,20 +390,20 @@ namespace ts.codefix {
|
||||
stringIndexContext?: UsageContext;
|
||||
}
|
||||
|
||||
export function inferTypeFromReferences(references: ReadonlyArray<Identifier>, checker: TypeChecker, cancellationToken: CancellationToken): Type | undefined {
|
||||
export function inferTypesFromReferences(references: ReadonlyArray<Identifier>, checker: TypeChecker, cancellationToken: CancellationToken): Type[] {
|
||||
const usageContext: UsageContext = {};
|
||||
for (const reference of references) {
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
inferTypeFromContext(reference, checker, usageContext);
|
||||
}
|
||||
return getTypeFromUsageContext(usageContext, checker);
|
||||
return inferFromContext(usageContext, checker);
|
||||
}
|
||||
|
||||
export function inferTypeForParametersFromReferences(references: ReadonlyArray<Identifier>, declaration: FunctionLikeDeclaration, checker: TypeChecker, cancellationToken: CancellationToken): (Type | undefined)[] | undefined {
|
||||
export function inferTypeForParametersFromReferences(references: ReadonlyArray<Identifier>, declaration: FunctionLikeDeclaration, program: Program, cancellationToken: CancellationToken): ParameterInference[] | undefined {
|
||||
const checker = program.getTypeChecker();
|
||||
if (references.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!declaration.parameters) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -262,15 +415,16 @@ namespace ts.codefix {
|
||||
}
|
||||
const isConstructor = declaration.kind === SyntaxKind.Constructor;
|
||||
const callContexts = isConstructor ? usageContext.constructContexts : usageContext.callContexts;
|
||||
return callContexts && declaration.parameters.map((parameter, parameterIndex) => {
|
||||
return callContexts && declaration.parameters.map((parameter, parameterIndex): ParameterInference => {
|
||||
const types: Type[] = [];
|
||||
const isRest = isRestParameter(parameter);
|
||||
let isOptional = false;
|
||||
for (const callContext of callContexts) {
|
||||
if (callContext.argumentTypes.length <= parameterIndex) {
|
||||
continue;
|
||||
isOptional = isInJSFile(declaration);
|
||||
types.push(checker.getUndefinedType());
|
||||
}
|
||||
|
||||
if (isRest) {
|
||||
else if (isRest) {
|
||||
for (let i = parameterIndex; i < callContext.argumentTypes.length; i++) {
|
||||
types.push(checker.getBaseTypeOfLiteralType(callContext.argumentTypes[i]));
|
||||
}
|
||||
@@ -279,11 +433,15 @@ namespace ts.codefix {
|
||||
types.push(checker.getBaseTypeOfLiteralType(callContext.argumentTypes[parameterIndex]));
|
||||
}
|
||||
}
|
||||
if (!types.length) {
|
||||
return undefined;
|
||||
if (isIdentifier(parameter.name)) {
|
||||
types.push(...inferTypesFromReferences(getReferences(parameter.name, program, cancellationToken), checker, cancellationToken));
|
||||
}
|
||||
const type = checker.getWidenedType(checker.getUnionType(types, UnionReduction.Subtype));
|
||||
return isRest ? checker.createArrayType(type) : type;
|
||||
const type = unifyFromContext(types, checker);
|
||||
return {
|
||||
type: isRest ? checker.createArrayType(type) : type,
|
||||
isOptional: isOptional && !isRest,
|
||||
declaration: parameter
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -352,7 +510,8 @@ namespace ts.codefix {
|
||||
break;
|
||||
|
||||
case SyntaxKind.PlusToken:
|
||||
usageContext.isNumberOrString = true;
|
||||
usageContext.isNumber = true;
|
||||
usageContext.isString = true;
|
||||
break;
|
||||
|
||||
// case SyntaxKind.ExclamationToken:
|
||||
@@ -423,7 +582,8 @@ namespace ts.codefix {
|
||||
usageContext.isString = true;
|
||||
}
|
||||
else {
|
||||
usageContext.isNumberOrString = true;
|
||||
usageContext.isNumber = true;
|
||||
usageContext.isString = true;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -497,7 +657,8 @@ namespace ts.codefix {
|
||||
|
||||
function inferTypeFromPropertyElementExpressionContext(parent: ElementAccessExpression, node: Expression, checker: TypeChecker, usageContext: UsageContext): void {
|
||||
if (node === parent.argumentExpression) {
|
||||
usageContext.isNumberOrString = true;
|
||||
usageContext.isNumber = true;
|
||||
usageContext.isString = true;
|
||||
return;
|
||||
}
|
||||
else {
|
||||
@@ -513,29 +674,83 @@ namespace ts.codefix {
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeFromUsageContext(usageContext: UsageContext, checker: TypeChecker): Type | undefined {
|
||||
if (usageContext.isNumberOrString && !usageContext.isNumber && !usageContext.isString) {
|
||||
return checker.getUnionType([checker.getNumberType(), checker.getStringType()]);
|
||||
export function unifyFromContext(inferences: ReadonlyArray<Type>, checker: TypeChecker, fallback = checker.getAnyType()): Type {
|
||||
if (!inferences.length) return fallback;
|
||||
const hasNonVacuousType = inferences.some(i => !(i.flags & (TypeFlags.Any | TypeFlags.Void)));
|
||||
const hasNonVacuousNonAnonymousType = inferences.some(
|
||||
i => !(i.flags & (TypeFlags.Nullable | TypeFlags.Any | TypeFlags.Void)) && !(checker.getObjectFlags(i) & ObjectFlags.Anonymous));
|
||||
const anons = inferences.filter(i => checker.getObjectFlags(i) & ObjectFlags.Anonymous) as AnonymousType[];
|
||||
const good = [];
|
||||
if (!hasNonVacuousNonAnonymousType && anons.length) {
|
||||
good.push(unifyAnonymousTypes(anons, checker));
|
||||
}
|
||||
else if (usageContext.isNumber) {
|
||||
return checker.getNumberType();
|
||||
good.push(...inferences.filter(i => !(checker.getObjectFlags(i) & ObjectFlags.Anonymous) && !(hasNonVacuousType && i.flags & (TypeFlags.Any | TypeFlags.Void))));
|
||||
return checker.getWidenedType(checker.getUnionType(good));
|
||||
}
|
||||
|
||||
function unifyAnonymousTypes(anons: AnonymousType[], checker: TypeChecker) {
|
||||
if (anons.length === 1) {
|
||||
return anons[0];
|
||||
}
|
||||
else if (usageContext.isString) {
|
||||
return checker.getStringType();
|
||||
const calls = [];
|
||||
const constructs = [];
|
||||
const stringIndices = [];
|
||||
const numberIndices = [];
|
||||
let stringIndexReadonly = false;
|
||||
let numberIndexReadonly = false;
|
||||
const props = createMultiMap<Type>();
|
||||
for (const anon of anons) {
|
||||
for (const p of checker.getPropertiesOfType(anon)) {
|
||||
props.add(p.name, checker.getTypeOfSymbolAtLocation(p, p.valueDeclaration));
|
||||
}
|
||||
calls.push(...checker.getSignaturesOfType(anon, SignatureKind.Call));
|
||||
constructs.push(...checker.getSignaturesOfType(anon, SignatureKind.Construct));
|
||||
if (anon.stringIndexInfo) {
|
||||
stringIndices.push(anon.stringIndexInfo.type);
|
||||
stringIndexReadonly = stringIndexReadonly || anon.stringIndexInfo.isReadonly;
|
||||
}
|
||||
if (anon.numberIndexInfo) {
|
||||
numberIndices.push(anon.numberIndexInfo.type);
|
||||
numberIndexReadonly = numberIndexReadonly || anon.numberIndexInfo.isReadonly;
|
||||
}
|
||||
}
|
||||
else if (usageContext.candidateTypes) {
|
||||
return checker.getWidenedType(checker.getUnionType(usageContext.candidateTypes.map(t => checker.getBaseTypeOfLiteralType(t)), UnionReduction.Subtype));
|
||||
const members = mapEntries(props, (name, types) => {
|
||||
const isOptional = types.length < anons.length ? SymbolFlags.Optional : 0;
|
||||
const s = checker.createSymbol(SymbolFlags.Property | isOptional, name as __String);
|
||||
s.type = checker.getUnionType(types);
|
||||
return [name, s];
|
||||
});
|
||||
return checker.createAnonymousType(
|
||||
anons[0].symbol,
|
||||
members as UnderscoreEscapedMap<TransientSymbol>,
|
||||
calls,
|
||||
constructs,
|
||||
stringIndices.length ? checker.createIndexInfo(checker.getUnionType(stringIndices), stringIndexReadonly) : undefined,
|
||||
numberIndices.length ? checker.createIndexInfo(checker.getUnionType(numberIndices), numberIndexReadonly) : undefined);
|
||||
}
|
||||
|
||||
function inferFromContext(usageContext: UsageContext, checker: TypeChecker) {
|
||||
const types = [];
|
||||
if (usageContext.isNumber) {
|
||||
types.push(checker.getNumberType());
|
||||
}
|
||||
else if (usageContext.properties && hasCallContext(usageContext.properties.get("then" as __String))) {
|
||||
if (usageContext.isString) {
|
||||
types.push(checker.getStringType());
|
||||
}
|
||||
|
||||
types.push(...(usageContext.candidateTypes || []).map(t => checker.getBaseTypeOfLiteralType(t)));
|
||||
|
||||
if (usageContext.properties && hasCallContext(usageContext.properties.get("then" as __String))) {
|
||||
const paramType = getParameterTypeFromCallContexts(0, usageContext.properties.get("then" as __String)!.callContexts!, /*isRestParameter*/ false, checker)!; // TODO: GH#18217
|
||||
const types = paramType.getCallSignatures().map(c => c.getReturnType());
|
||||
return checker.createPromiseType(types.length ? checker.getUnionType(types, UnionReduction.Subtype) : checker.getAnyType());
|
||||
types.push(checker.createPromiseType(types.length ? checker.getUnionType(types, UnionReduction.Subtype) : checker.getAnyType()));
|
||||
}
|
||||
else if (usageContext.properties && hasCallContext(usageContext.properties.get("push" as __String))) {
|
||||
return checker.createArrayType(getParameterTypeFromCallContexts(0, usageContext.properties.get("push" as __String)!.callContexts!, /*isRestParameter*/ false, checker)!);
|
||||
types.push(checker.createArrayType(getParameterTypeFromCallContexts(0, usageContext.properties.get("push" as __String)!.callContexts!, /*isRestParameter*/ false, checker)!));
|
||||
}
|
||||
else if (usageContext.numberIndexContext) {
|
||||
return checker.createArrayType(recur(usageContext.numberIndexContext));
|
||||
|
||||
if (usageContext.numberIndexContext) {
|
||||
return [checker.createArrayType(recur(usageContext.numberIndexContext))];
|
||||
}
|
||||
else if (usageContext.properties || usageContext.callContexts || usageContext.constructContexts || usageContext.stringIndexContext) {
|
||||
const members = createUnderscoreEscapedMap<Symbol>();
|
||||
@@ -567,14 +782,12 @@ namespace ts.codefix {
|
||||
stringIndexInfo = checker.createIndexInfo(recur(usageContext.stringIndexContext), /*isReadonly*/ false);
|
||||
}
|
||||
|
||||
return checker.createAnonymousType(/*symbol*/ undefined!, members, callSignatures, constructSignatures, stringIndexInfo, /*numberIndexInfo*/ undefined); // TODO: GH#18217
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
types.push(checker.createAnonymousType(/*symbol*/ undefined!, members, callSignatures, constructSignatures, stringIndexInfo, /*numberIndexInfo*/ undefined)); // TODO: GH#18217
|
||||
}
|
||||
return types;
|
||||
|
||||
function recur(innerContext: UsageContext): Type {
|
||||
return getTypeFromUsageContext(innerContext, checker) || checker.getAnyType();
|
||||
return unifyFromContext(inferFromContext(innerContext, checker), checker);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -607,7 +820,7 @@ namespace ts.codefix {
|
||||
symbol.type = checker.getWidenedType(checker.getBaseTypeOfLiteralType(callContext.argumentTypes[i]));
|
||||
parameters.push(symbol);
|
||||
}
|
||||
const returnType = getTypeFromUsageContext(callContext.returnType, checker) || checker.getVoidType();
|
||||
const returnType = unifyFromContext(inferFromContext(callContext.returnType, checker), checker, checker.getVoidType());
|
||||
// TODO: GH#18217
|
||||
return checker.createSignature(/*declaration*/ undefined!, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, callContext.argumentTypes.length, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
|
||||
}
|
||||
|
||||
+30
-295
@@ -37,18 +37,13 @@ namespace ts.Completions {
|
||||
export function getCompletionsAtPosition(host: LanguageServiceHost, program: Program, log: Log, sourceFile: SourceFile, position: number, preferences: UserPreferences, triggerCharacter: CompletionsTriggerCharacter | undefined): CompletionInfo | undefined {
|
||||
const typeChecker = program.getTypeChecker();
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
if (isInReferenceComment(sourceFile, position)) {
|
||||
const entries = PathCompletions.getTripleSlashReferenceCompletion(sourceFile, position, compilerOptions, host);
|
||||
return entries && convertPathCompletions(entries);
|
||||
}
|
||||
|
||||
const contextToken = findPrecedingToken(position, sourceFile);
|
||||
if (triggerCharacter && !isValidTrigger(sourceFile, triggerCharacter, contextToken, position)) return undefined;
|
||||
|
||||
if (isInString(sourceFile, position, contextToken)) {
|
||||
return !contextToken || !isStringLiteralLike(contextToken)
|
||||
? undefined
|
||||
: convertStringLiteralCompletions(getStringLiteralCompletionEntries(sourceFile, contextToken, position, typeChecker, compilerOptions, host), sourceFile, typeChecker, log, preferences);
|
||||
const stringCompletions = StringCompletions.getStringLiteralCompletions(sourceFile, position, contextToken, typeChecker, compilerOptions, host, log, preferences);
|
||||
if (stringCompletions) {
|
||||
return stringCompletions;
|
||||
}
|
||||
|
||||
if (contextToken && isBreakOrContinueStatement(contextToken.parent)
|
||||
@@ -77,34 +72,6 @@ namespace ts.Completions {
|
||||
}
|
||||
}
|
||||
|
||||
function convertStringLiteralCompletions(completion: StringLiteralCompletion | undefined, sourceFile: SourceFile, checker: TypeChecker, log: Log, preferences: UserPreferences): CompletionInfo | undefined {
|
||||
if (completion === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
switch (completion.kind) {
|
||||
case StringLiteralCompletionKind.Paths:
|
||||
return convertPathCompletions(completion.paths);
|
||||
case StringLiteralCompletionKind.Properties: {
|
||||
const entries: CompletionEntry[] = [];
|
||||
getCompletionEntriesFromSymbols(completion.symbols, entries, sourceFile, sourceFile, checker, ScriptTarget.ESNext, log, CompletionKind.String, preferences); // Target will not be used, so arbitrary
|
||||
return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: completion.hasIndexSignature, entries };
|
||||
}
|
||||
case StringLiteralCompletionKind.Types: {
|
||||
const entries = completion.types.map(type => ({ name: type.value, kindModifiers: ScriptElementKindModifier.none, kind: ScriptElementKind.string, sortText: "0" }));
|
||||
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: completion.isNewIdentifier, entries };
|
||||
}
|
||||
default:
|
||||
return Debug.assertNever(completion);
|
||||
}
|
||||
}
|
||||
|
||||
function convertPathCompletions(pathCompletions: ReadonlyArray<PathCompletions.PathCompletion>): CompletionInfo {
|
||||
const isGlobalCompletion = false; // We don't want the editor to offer any other completions, such as snippets, inside a comment.
|
||||
const isNewIdentifierLocation = true; // The user may type in a path that doesn't yet exist, creating a "new identifier" with respect to the collection of identifiers the server is aware of.
|
||||
const entries = pathCompletions.map(({ name, kind, span }) => ({ name, kind, kindModifiers: ScriptElementKindModifier.none, sortText: "0", replacementSpan: span }));
|
||||
return { isGlobalCompletion, isMemberCompletion: false, isNewIdentifierLocation, entries };
|
||||
}
|
||||
|
||||
function jsdocCompletionInfo(entries: CompletionEntry[]): CompletionInfo {
|
||||
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries };
|
||||
}
|
||||
@@ -198,8 +165,9 @@ namespace ts.Completions {
|
||||
});
|
||||
}
|
||||
|
||||
const completionNameForLiteral = JSON.stringify;
|
||||
function createCompletionEntryForLiteral(literal: string | number): CompletionEntry {
|
||||
const completionNameForLiteral = (literal: string | number | PseudoBigInt) =>
|
||||
typeof literal === "object" ? pseudoBigIntToString(literal) + "n" : JSON.stringify(literal);
|
||||
function createCompletionEntryForLiteral(literal: string | number | PseudoBigInt): CompletionEntry {
|
||||
return { name: completionNameForLiteral(literal), kind: ScriptElementKind.string, kindModifiers: ScriptElementKindModifier.none, sortText: "0" };
|
||||
}
|
||||
|
||||
@@ -270,22 +238,6 @@ namespace ts.Completions {
|
||||
};
|
||||
}
|
||||
|
||||
function quote(text: string, preferences: UserPreferences): string {
|
||||
if (/^\d+$/.test(text)) {
|
||||
return text;
|
||||
}
|
||||
const quoted = JSON.stringify(text);
|
||||
switch (preferences.quotePreference) {
|
||||
case undefined:
|
||||
case "double":
|
||||
return quoted;
|
||||
case "single":
|
||||
return `'${stripQuotes(quoted).replace("'", "\\'").replace('\\"', '"')}'`;
|
||||
default:
|
||||
return Debug.assertNever(preferences.quotePreference);
|
||||
}
|
||||
}
|
||||
|
||||
function isRecommendedCompletionMatch(localSymbol: Symbol, recommendedCompletion: Symbol | undefined, checker: TypeChecker): boolean {
|
||||
return localSymbol === recommendedCompletion ||
|
||||
!!(localSymbol.flags & SymbolFlags.ExportValue) && checker.getExportSymbolOfSymbol(localSymbol) === recommendedCompletion;
|
||||
@@ -299,7 +251,7 @@ namespace ts.Completions {
|
||||
return origin && originIsExport(origin) ? stripQuotes(origin.moduleSymbol.name) : undefined;
|
||||
}
|
||||
|
||||
function getCompletionEntriesFromSymbols(
|
||||
export function getCompletionEntriesFromSymbols(
|
||||
symbols: ReadonlyArray<Symbol>,
|
||||
entries: Push<CompletionEntry>,
|
||||
location: Node | undefined,
|
||||
@@ -377,145 +329,6 @@ namespace ts.Completions {
|
||||
return entries;
|
||||
}
|
||||
|
||||
const enum StringLiteralCompletionKind { Paths, Properties, Types }
|
||||
interface StringLiteralCompletionsFromProperties {
|
||||
readonly kind: StringLiteralCompletionKind.Properties;
|
||||
readonly symbols: ReadonlyArray<Symbol>;
|
||||
readonly hasIndexSignature: boolean;
|
||||
}
|
||||
interface StringLiteralCompletionsFromTypes {
|
||||
readonly kind: StringLiteralCompletionKind.Types;
|
||||
readonly types: ReadonlyArray<StringLiteralType>;
|
||||
readonly isNewIdentifier: boolean;
|
||||
}
|
||||
type StringLiteralCompletion = { readonly kind: StringLiteralCompletionKind.Paths, readonly paths: ReadonlyArray<PathCompletions.PathCompletion> } | StringLiteralCompletionsFromProperties | StringLiteralCompletionsFromTypes;
|
||||
function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringLiteralLike, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost): StringLiteralCompletion | undefined {
|
||||
const { parent } = node;
|
||||
switch (parent.kind) {
|
||||
case SyntaxKind.LiteralType:
|
||||
switch (parent.parent.kind) {
|
||||
case SyntaxKind.TypeReference:
|
||||
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(parent as LiteralTypeNode)), isNewIdentifier: false };
|
||||
case SyntaxKind.IndexedAccessType:
|
||||
// Get all apparent property names
|
||||
// i.e. interface Foo {
|
||||
// foo: string;
|
||||
// bar: string;
|
||||
// }
|
||||
// let x: Foo["/*completion position*/"]
|
||||
return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode((parent.parent as IndexedAccessTypeNode).objectType));
|
||||
case SyntaxKind.ImportType:
|
||||
return { kind: StringLiteralCompletionKind.Paths, paths: PathCompletions.getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker) };
|
||||
case SyntaxKind.UnionType: {
|
||||
if (!isTypeReferenceNode(parent.parent.parent)) return undefined;
|
||||
const alreadyUsedTypes = getAlreadyUsedTypesInStringLiteralUnion(parent.parent as UnionTypeNode, parent as LiteralTypeNode);
|
||||
const types = getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(parent.parent as UnionTypeNode)).filter(t => !contains(alreadyUsedTypes, t.value));
|
||||
return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier: false };
|
||||
}
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
||||
case SyntaxKind.PropertyAssignment:
|
||||
if (isObjectLiteralExpression(parent.parent) && (<PropertyAssignment>parent).name === node) {
|
||||
// Get quoted name of properties of the object literal expression
|
||||
// i.e. interface ConfigFiles {
|
||||
// 'jspm:dev': string
|
||||
// }
|
||||
// let files: ConfigFiles = {
|
||||
// '/*completion position*/'
|
||||
// }
|
||||
//
|
||||
// function foo(c: ConfigFiles) {}
|
||||
// foo({
|
||||
// '/*completion position*/'
|
||||
// });
|
||||
return stringLiteralCompletionsFromProperties(typeChecker.getContextualType(parent.parent));
|
||||
}
|
||||
return fromContextualType();
|
||||
|
||||
case SyntaxKind.ElementAccessExpression: {
|
||||
const { expression, argumentExpression } = parent as ElementAccessExpression;
|
||||
if (node === argumentExpression) {
|
||||
// Get all names of properties on the expression
|
||||
// i.e. interface A {
|
||||
// 'prop1': string
|
||||
// }
|
||||
// let a: A;
|
||||
// a['/*completion position*/']
|
||||
return stringLiteralCompletionsFromProperties(typeChecker.getTypeAtLocation(expression));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
case SyntaxKind.CallExpression:
|
||||
case SyntaxKind.NewExpression:
|
||||
if (!isRequireCall(parent, /*checkArgumentIsStringLiteralLike*/ false) && !isImportCall(parent)) {
|
||||
const argumentInfo = SignatureHelp.getArgumentInfoForCompletions(node, position, sourceFile);
|
||||
// Get string literal completions from specialized signatures of the target
|
||||
// i.e. declare function f(a: 'A');
|
||||
// f("/*completion position*/")
|
||||
return argumentInfo ? getStringLiteralCompletionsFromSignature(argumentInfo, typeChecker) : fromContextualType();
|
||||
}
|
||||
// falls through (is `require("")` or `import("")`)
|
||||
|
||||
case SyntaxKind.ImportDeclaration:
|
||||
case SyntaxKind.ExportDeclaration:
|
||||
case SyntaxKind.ExternalModuleReference:
|
||||
// Get all known external module names or complete a path to a module
|
||||
// i.e. import * as ns from "/*completion position*/";
|
||||
// var y = import("/*completion position*/");
|
||||
// import x = require("/*completion position*/");
|
||||
// var y = require("/*completion position*/");
|
||||
// export * from "/*completion position*/";
|
||||
return { kind: StringLiteralCompletionKind.Paths, paths: PathCompletions.getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker) };
|
||||
|
||||
default:
|
||||
return fromContextualType();
|
||||
}
|
||||
|
||||
function fromContextualType(): StringLiteralCompletion {
|
||||
// Get completion for string literal from string literal type
|
||||
// i.e. var x: "hi" | "hello" = "/*completion position*/"
|
||||
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker)), isNewIdentifier: false };
|
||||
}
|
||||
}
|
||||
|
||||
function getAlreadyUsedTypesInStringLiteralUnion(union: UnionTypeNode, current: LiteralTypeNode): ReadonlyArray<string> {
|
||||
return mapDefined(union.types, type =>
|
||||
type !== current && isLiteralTypeNode(type) && isStringLiteral(type.literal) ? type.literal.text : undefined);
|
||||
}
|
||||
|
||||
function getStringLiteralCompletionsFromSignature(argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker): StringLiteralCompletionsFromTypes {
|
||||
let isNewIdentifier = false;
|
||||
|
||||
const uniques = createMap<true>();
|
||||
const candidates: Signature[] = [];
|
||||
checker.getResolvedSignature(argumentInfo.invocation, candidates, argumentInfo.argumentCount);
|
||||
const types = flatMap(candidates, candidate => {
|
||||
if (!candidate.hasRestParameter && argumentInfo.argumentCount > candidate.parameters.length) return;
|
||||
const type = checker.getParameterType(candidate, argumentInfo.argumentIndex);
|
||||
isNewIdentifier = isNewIdentifier || !!(type.flags & TypeFlags.String);
|
||||
return getStringLiteralTypes(type, uniques);
|
||||
});
|
||||
|
||||
return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier };
|
||||
}
|
||||
|
||||
function stringLiteralCompletionsFromProperties(type: Type | undefined): StringLiteralCompletionsFromProperties | undefined {
|
||||
return type && { kind: StringLiteralCompletionKind.Properties, symbols: type.getApparentProperties(), hasIndexSignature: hasIndexSignature(type) };
|
||||
}
|
||||
|
||||
function getStringLiteralTypes(type: Type | undefined, uniques = createMap<true>()): ReadonlyArray<StringLiteralType> {
|
||||
if (!type) return emptyArray;
|
||||
type = skipConstraint(type);
|
||||
return type.isUnion()
|
||||
? flatMap(type.types, t => getStringLiteralTypes(t, uniques))
|
||||
: type.isStringLiteral() && !(type.flags & TypeFlags.EnumLiteral) && addToSeen(uniques, type.value)
|
||||
? [type]
|
||||
: emptyArray;
|
||||
}
|
||||
|
||||
interface SymbolCompletion {
|
||||
type: "symbol";
|
||||
symbol: Symbol;
|
||||
@@ -525,7 +338,7 @@ namespace ts.Completions {
|
||||
readonly isJsxInitializer: IsJsxInitializer;
|
||||
}
|
||||
function getSymbolCompletionFromEntryId(program: Program, log: Log, sourceFile: SourceFile, position: number, entryId: CompletionEntryIdentifier,
|
||||
): SymbolCompletion | { type: "request", request: Request } | { type: "literal", literal: string | number } | { type: "none" } {
|
||||
): SymbolCompletion | { type: "request", request: Request } | { type: "literal", literal: string | number | PseudoBigInt } | { type: "none" } {
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
const completionData = getCompletionData(program, log, sourceFile, isUncheckedFile(sourceFile, compilerOptions), position, { includeCompletionsForModuleExports: true, includeCompletionsWithInsertText: true }, entryId);
|
||||
if (!completionData) {
|
||||
@@ -583,10 +396,7 @@ namespace ts.Completions {
|
||||
|
||||
const contextToken = findPrecedingToken(position, sourceFile);
|
||||
if (isInString(sourceFile, position, contextToken)) {
|
||||
const stringLiteralCompletions = !contextToken || !isStringLiteralLike(contextToken)
|
||||
? undefined
|
||||
: getStringLiteralCompletionEntries(sourceFile, contextToken, position, typeChecker, compilerOptions, host);
|
||||
return stringLiteralCompletions && stringLiteralCompletionDetails(name, contextToken!, stringLiteralCompletions, sourceFile, typeChecker, cancellationToken); // TODO: GH#18217
|
||||
return StringCompletions.getStringLiteralCompletionDetails(name, sourceFile, position, contextToken, typeChecker, compilerOptions, host, cancellationToken);
|
||||
}
|
||||
|
||||
// Compute all the completion symbols again.
|
||||
@@ -626,7 +436,7 @@ namespace ts.Completions {
|
||||
return createCompletionDetails(name, ScriptElementKindModifier.none, kind, [displayPart(name, kind2)]);
|
||||
}
|
||||
|
||||
function createCompletionDetailsForSymbol(symbol: Symbol, checker: TypeChecker, sourceFile: SourceFile, location: Node, cancellationToken: CancellationToken, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[]): CompletionEntryDetails {
|
||||
export function createCompletionDetailsForSymbol(symbol: Symbol, checker: TypeChecker, sourceFile: SourceFile, location: Node, cancellationToken: CancellationToken, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[]): CompletionEntryDetails {
|
||||
const { displayParts, documentation, symbolKind, tags } =
|
||||
checker.runWithCancellationToken(cancellationToken, checker =>
|
||||
SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, sourceFile, location, location, SemanticMeaning.All)
|
||||
@@ -634,24 +444,7 @@ namespace ts.Completions {
|
||||
return createCompletionDetails(symbol.name, SymbolDisplay.getSymbolModifiers(symbol), symbolKind, displayParts, documentation, tags, codeActions, sourceDisplay);
|
||||
}
|
||||
|
||||
function stringLiteralCompletionDetails(name: string, location: Node, completion: StringLiteralCompletion, sourceFile: SourceFile, checker: TypeChecker, cancellationToken: CancellationToken): CompletionEntryDetails | undefined {
|
||||
switch (completion.kind) {
|
||||
case StringLiteralCompletionKind.Paths: {
|
||||
const match = find(completion.paths, p => p.name === name);
|
||||
return match && createCompletionDetails(name, ScriptElementKindModifier.none, match.kind, [textPart(name)]);
|
||||
}
|
||||
case StringLiteralCompletionKind.Properties: {
|
||||
const match = find(completion.symbols, s => s.name === name);
|
||||
return match && createCompletionDetailsForSymbol(match, checker, sourceFile, location, cancellationToken);
|
||||
}
|
||||
case StringLiteralCompletionKind.Types:
|
||||
return find(completion.types, t => t.value === name) ? createCompletionDetails(name, ScriptElementKindModifier.none, ScriptElementKind.typeElement, [textPart(name)]) : undefined;
|
||||
default:
|
||||
return Debug.assertNever(completion);
|
||||
}
|
||||
}
|
||||
|
||||
function createCompletionDetails(name: string, kindModifiers: string, kind: ScriptElementKind, displayParts: SymbolDisplayPart[], documentation?: SymbolDisplayPart[], tags?: JSDocTagInfo[], codeActions?: CodeAction[], source?: SymbolDisplayPart[]): CompletionEntryDetails {
|
||||
export function createCompletionDetails(name: string, kindModifiers: string, kind: ScriptElementKind, displayParts: SymbolDisplayPart[], documentation?: SymbolDisplayPart[], tags?: JSDocTagInfo[], codeActions?: CodeAction[], source?: SymbolDisplayPart[]): CompletionEntryDetails {
|
||||
return { name, kindModifiers, kind, displayParts, documentation, tags, codeActions, source };
|
||||
}
|
||||
|
||||
@@ -710,7 +503,7 @@ namespace ts.Completions {
|
||||
readonly isNewIdentifierLocation: boolean;
|
||||
readonly location: Node | undefined;
|
||||
readonly keywordFilters: KeywordCompletionFilters;
|
||||
readonly literals: ReadonlyArray<string | number>;
|
||||
readonly literals: ReadonlyArray<string | number | PseudoBigInt>;
|
||||
readonly symbolToOriginInfoMap: SymbolOriginInfoMap;
|
||||
readonly recommendedCompletion: Symbol | undefined;
|
||||
readonly previousToken: Node | undefined;
|
||||
@@ -718,7 +511,7 @@ namespace ts.Completions {
|
||||
}
|
||||
type Request = { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag } | { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag };
|
||||
|
||||
const enum CompletionKind {
|
||||
export const enum CompletionKind {
|
||||
ObjectPropertyDeclaration,
|
||||
Global,
|
||||
PropertyAccess,
|
||||
@@ -772,28 +565,6 @@ namespace ts.Completions {
|
||||
}
|
||||
}
|
||||
|
||||
function getContextualTypeFromParent(node: Expression, checker: TypeChecker): Type | undefined {
|
||||
const { parent } = node;
|
||||
switch (parent.kind) {
|
||||
case SyntaxKind.NewExpression:
|
||||
return checker.getContextualType(parent as NewExpression);
|
||||
case SyntaxKind.BinaryExpression: {
|
||||
const { left, operatorToken, right } = parent as BinaryExpression;
|
||||
return isEqualityOperatorKind(operatorToken.kind)
|
||||
? checker.getTypeAtLocation(node === right ? left : right)
|
||||
: checker.getContextualType(node);
|
||||
}
|
||||
case SyntaxKind.CaseClause:
|
||||
return (parent as CaseClause).expression === node ? getSwitchedType(parent as CaseClause, checker) : undefined;
|
||||
default:
|
||||
return checker.getContextualType(node);
|
||||
}
|
||||
}
|
||||
|
||||
function getSwitchedType(caseClause: CaseClause, checker: TypeChecker): Type | undefined {
|
||||
return checker.getTypeAtLocation(caseClause.parent.parent.expression);
|
||||
}
|
||||
|
||||
function getFirstSymbolInChain(symbol: Symbol, enclosingDeclaration: Node, checker: TypeChecker): Symbol | undefined {
|
||||
const chain = checker.getAccessibleSymbolChain(symbol, enclosingDeclaration, /*meaning*/ SymbolFlags.All, /*useOnlyExternalAliasing*/ false);
|
||||
if (chain) return first(chain);
|
||||
@@ -1200,9 +971,9 @@ namespace ts.Completions {
|
||||
function tryGetJsxCompletionSymbols(): GlobalsSearch {
|
||||
const jsxContainer = tryGetContainingJsxElement(contextToken);
|
||||
// Cursor is inside a JSX self-closing element or opening element
|
||||
const attrsType = jsxContainer && typeChecker.getAllAttributesTypeFromJsxOpeningLikeElement(jsxContainer);
|
||||
const attrsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes);
|
||||
if (!attrsType) return GlobalsSearch.Continue;
|
||||
symbols = filterJsxAttributes(typeChecker.getPropertiesOfType(attrsType), jsxContainer!.attributes.properties);
|
||||
symbols = filterJsxAttributes(getPropertiesForObjectExpression(attrsType, jsxContainer!.attributes, typeChecker), jsxContainer!.attributes.properties);
|
||||
completionKind = CompletionKind.MemberLike;
|
||||
isNewIdentifierLocation = false;
|
||||
return GlobalsSearch.Success;
|
||||
@@ -1358,23 +1129,13 @@ namespace ts.Completions {
|
||||
return false;
|
||||
}
|
||||
|
||||
function symbolCanBeReferencedAtTypeLocation(symbol: Symbol): boolean {
|
||||
symbol = symbol.exportSymbol || symbol;
|
||||
|
||||
// This is an alias, follow what it aliases
|
||||
symbol = skipAlias(symbol, typeChecker);
|
||||
|
||||
if (symbol.flags & SymbolFlags.Type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (symbol.flags & SymbolFlags.Module) {
|
||||
const exportedSymbols = typeChecker.getExportsOfModule(symbol);
|
||||
// If the exported symbols contains type,
|
||||
// symbol can be referenced at locations where type is allowed
|
||||
return exportedSymbols.some(symbolCanBeReferencedAtTypeLocation);
|
||||
}
|
||||
return false;
|
||||
/** True if symbol is a type or a module containing at least one type. */
|
||||
function symbolCanBeReferencedAtTypeLocation(symbol: Symbol, seenModules = createMap<true>()): boolean {
|
||||
const sym = skipAlias(symbol.exportSymbol || symbol, typeChecker);
|
||||
return !!(sym.flags & SymbolFlags.Type) ||
|
||||
!!(sym.flags & SymbolFlags.Module) &&
|
||||
addToSeen(seenModules, getSymbolId(sym)) &&
|
||||
typeChecker.getExportsOfModule(sym).some(e => symbolCanBeReferencedAtTypeLocation(e, seenModules));
|
||||
}
|
||||
|
||||
function getSymbolsFromOtherSourceFileExports(symbols: Symbol[], tokenText: string, target: ScriptTarget): void {
|
||||
@@ -1407,14 +1168,16 @@ namespace ts.Completions {
|
||||
// The actual import fix might end up coming from a re-export -- we don't compute that until getting completion details.
|
||||
// This is just to avoid adding duplicate completion entries.
|
||||
//
|
||||
// If `symbol.parent !== ...`, this comes from an `export * from "foo"` re-export. Those don't create new symbols.
|
||||
// If `some(...)`, this comes from an `export { foo } from "foo"` re-export, which creates a new symbol (thus isn't caught by the first check).
|
||||
// If `symbol.parent !== ...`, this is an `export * from "foo"` re-export. Those don't create new symbols.
|
||||
if (typeChecker.getMergedSymbol(symbol.parent!) !== resolvedModuleSymbol
|
||||
|| some(symbol.declarations, d => isExportSpecifier(d) && !!d.parent.parent.moduleSpecifier)) {
|
||||
|| some(symbol.declarations, d =>
|
||||
// If `!!d.name.originalKeywordKind`, this is `export { _break as break };` -- skip this and prefer the keyword completion.
|
||||
// If `!!d.parent.parent.moduleSpecifier`, this is `export { foo } from "foo"` re-export, which creates a new symbol (thus isn't caught by the first check).
|
||||
isExportSpecifier(d) && (d.propertyName ? isIdentifierANonContextualKeyword(d.name) : !!d.parent.parent.moduleSpecifier))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isDefaultExport = symbol.name === InternalSymbolName.Default;
|
||||
const isDefaultExport = symbol.escapedName === InternalSymbolName.Default;
|
||||
if (isDefaultExport) {
|
||||
symbol = getLocalSymbolForExportDefault(symbol) || symbol;
|
||||
}
|
||||
@@ -2150,7 +1913,7 @@ namespace ts.Completions {
|
||||
case KeywordCompletionFilters.None:
|
||||
return false;
|
||||
case KeywordCompletionFilters.All:
|
||||
return kind === SyntaxKind.AsyncKeyword || !isContextualKeyword(kind) && !isClassMemberCompletionKeyword(kind) || kind === SyntaxKind.DeclareKeyword || kind === SyntaxKind.ModuleKeyword
|
||||
return kind === SyntaxKind.AsyncKeyword || SyntaxKind.AwaitKeyword || !isContextualKeyword(kind) && !isClassMemberCompletionKeyword(kind) || kind === SyntaxKind.DeclareKeyword || kind === SyntaxKind.ModuleKeyword
|
||||
|| isTypeKeyword(kind) && kind !== SyntaxKind.UndefinedKeyword;
|
||||
case KeywordCompletionFilters.ClassElementKeywords:
|
||||
return isClassMemberCompletionKeyword(kind);
|
||||
@@ -2193,25 +1956,13 @@ namespace ts.Completions {
|
||||
return isIdentifier(node) ? node.originalKeywordKind || SyntaxKind.Unknown : node.kind;
|
||||
}
|
||||
|
||||
function isEqualityOperatorKind(kind: SyntaxKind): kind is EqualityOperator {
|
||||
switch (kind) {
|
||||
case SyntaxKind.EqualsEqualsEqualsToken:
|
||||
case SyntaxKind.EqualsEqualsToken:
|
||||
case SyntaxKind.ExclamationEqualsEqualsToken:
|
||||
case SyntaxKind.ExclamationEqualsToken:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 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, obj: ObjectLiteralExpression, checker: TypeChecker): Symbol[] {
|
||||
function getPropertiesForObjectExpression(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] {
|
||||
return contextualType.isUnion()
|
||||
? checker.getAllPossiblePropertiesOfTypes(contextualType.types.filter(memberType =>
|
||||
// If we're providing completions for an object literal, skip primitive, array-like, or callable types since those shouldn't be implemented by object literals.
|
||||
@@ -2272,10 +2023,6 @@ namespace ts.Completions {
|
||||
return node.parent && isClassOrTypeElement(node.parent) && isObjectTypeDeclaration(node.parent.parent);
|
||||
}
|
||||
|
||||
function hasIndexSignature(type: Type): boolean {
|
||||
return !!type.getStringIndexType() || !!type.getNumberIndexType();
|
||||
}
|
||||
|
||||
function isValidTrigger(sourceFile: SourceFile, triggerCharacter: CompletionsTriggerCharacter, contextToken: Node | undefined, position: number): boolean {
|
||||
switch (triggerCharacter) {
|
||||
case ".":
|
||||
@@ -2301,16 +2048,4 @@ namespace ts.Completions {
|
||||
function binaryExpressionMayBeOpenTag({ left }: BinaryExpression): boolean {
|
||||
return nodeIsMissing(left);
|
||||
}
|
||||
|
||||
function isStringLiteralOrTemplate(node: Node): node is StringLiteralLike | TemplateExpression | TaggedTemplateExpression {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.StringLiteral:
|
||||
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
||||
case SyntaxKind.TemplateExpression:
|
||||
case SyntaxKind.TaggedTemplateExpression:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,14 +121,6 @@ namespace ts {
|
||||
const buckets = createMap<Map<DocumentRegistryEntry>>();
|
||||
const getCanonicalFileName = createGetCanonicalFileName(!!useCaseSensitiveFileNames);
|
||||
|
||||
function getBucketForCompilationSettings(key: DocumentRegistryBucketKey, createIfMissing: boolean): Map<DocumentRegistryEntry> {
|
||||
let bucket = buckets.get(key);
|
||||
if (!bucket && createIfMissing) {
|
||||
buckets.set(key, bucket = createMap<DocumentRegistryEntry>());
|
||||
}
|
||||
return bucket!; // TODO: GH#18217
|
||||
}
|
||||
|
||||
function reportStats() {
|
||||
const bucketInfoArray = arrayFrom(buckets.keys()).filter(name => name && name.charAt(0) === "_").map(name => {
|
||||
const entries = buckets.get(name)!;
|
||||
@@ -178,7 +170,7 @@ namespace ts {
|
||||
acquiring: boolean,
|
||||
scriptKind?: ScriptKind): SourceFile {
|
||||
|
||||
const bucket = getBucketForCompilationSettings(key, /*createIfMissing*/ true);
|
||||
const bucket = getOrUpdate<Map<DocumentRegistryEntry>>(buckets, key, createMap);
|
||||
let entry = bucket.get(path);
|
||||
const scriptTarget = scriptKind === ScriptKind.JSON ? ScriptTarget.JSON : compilationSettings.target || ScriptTarget.ES5;
|
||||
if (!entry && externalCache) {
|
||||
@@ -238,9 +230,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
function releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey): void {
|
||||
const bucket = getBucketForCompilationSettings(key, /*createIfMissing*/ false);
|
||||
Debug.assert(bucket !== undefined);
|
||||
|
||||
const bucket = Debug.assertDefined(buckets.get(key));
|
||||
const entry = bucket.get(path)!;
|
||||
entry.languageServiceRefCount--;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* @internal */
|
||||
namespace ts.FindAllReferences {
|
||||
export interface SymbolAndEntries {
|
||||
definition: Definition | undefined;
|
||||
references: Entry[];
|
||||
readonly definition: Definition | undefined;
|
||||
readonly references: ReadonlyArray<Entry>;
|
||||
}
|
||||
|
||||
export const enum DefinitionKind { Symbol, Label, Keyword, This, String }
|
||||
@@ -60,7 +60,7 @@ namespace ts.FindAllReferences {
|
||||
return map(referenceEntries, entry => toImplementationLocation(entry, checker));
|
||||
}
|
||||
|
||||
function getImplementationReferenceEntries(program: Program, cancellationToken: CancellationToken, sourceFiles: ReadonlyArray<SourceFile>, node: Node, position: number): Entry[] | undefined {
|
||||
function getImplementationReferenceEntries(program: Program, cancellationToken: CancellationToken, sourceFiles: ReadonlyArray<SourceFile>, node: Node, position: number): ReadonlyArray<Entry> | undefined {
|
||||
if (node.kind === SyntaxKind.SourceFile) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -94,11 +94,19 @@ namespace ts.FindAllReferences {
|
||||
|
||||
export type ToReferenceOrRenameEntry<T> = (entry: Entry, originalNode: Node) => T;
|
||||
|
||||
export function getReferenceEntriesForNode(position: number, node: Node, program: Program, sourceFiles: ReadonlyArray<SourceFile>, cancellationToken: CancellationToken, options: Options = {}, sourceFilesSet: ReadonlyMap<true> = arrayToSet(sourceFiles, f => f.fileName)): Entry[] | undefined {
|
||||
export function getReferenceEntriesForNode(
|
||||
position: number,
|
||||
node: Node,
|
||||
program: Program,
|
||||
sourceFiles: ReadonlyArray<SourceFile>,
|
||||
cancellationToken: CancellationToken,
|
||||
options: Options = {},
|
||||
sourceFilesSet: ReadonlyMap<true> = arrayToSet(sourceFiles, f => f.fileName),
|
||||
): ReadonlyArray<Entry> | undefined {
|
||||
return flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options, sourceFilesSet));
|
||||
}
|
||||
|
||||
function flattenEntries(referenceSymbols: SymbolAndEntries[] | undefined): Entry[] | undefined {
|
||||
function flattenEntries(referenceSymbols: SymbolAndEntries[] | undefined): ReadonlyArray<Entry> | undefined {
|
||||
return referenceSymbols && flatMap(referenceSymbols, r => r.references);
|
||||
}
|
||||
|
||||
@@ -143,7 +151,7 @@ namespace ts.FindAllReferences {
|
||||
|
||||
function getDefinitionKindAndDisplayParts(symbol: Symbol, checker: TypeChecker, node: Node): { displayParts: SymbolDisplayPart[], kind: ScriptElementKind } {
|
||||
const meaning = Core.getIntersectingMeaningFromDeclarations(node, symbol);
|
||||
const enclosingDeclaration = firstOrUndefined(symbol.declarations) || node;
|
||||
const enclosingDeclaration = symbol.declarations && firstOrUndefined(symbol.declarations) || node;
|
||||
const { displayParts, symbolKind } =
|
||||
SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, enclosingDeclaration.getSourceFile(), enclosingDeclaration, enclosingDeclaration, meaning);
|
||||
return { displayParts, kind: symbolKind };
|
||||
@@ -360,6 +368,10 @@ namespace ts.FindAllReferences.Core {
|
||||
return !options.implementations && isStringLiteral(node) ? getReferencesForStringLiteral(node, sourceFiles, cancellationToken) : undefined;
|
||||
}
|
||||
|
||||
if (symbol.escapedName === InternalSymbolName.ExportEquals) {
|
||||
return getReferencedSymbolsForModule(program, symbol.parent!, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet);
|
||||
}
|
||||
|
||||
let moduleReferences: SymbolAndEntries[] = emptyArray;
|
||||
const moduleSourceFile = isModuleSymbol(symbol);
|
||||
let referencedNode: Node | undefined = node;
|
||||
@@ -419,6 +431,22 @@ namespace ts.FindAllReferences.Core {
|
||||
}
|
||||
}
|
||||
|
||||
const exported = symbol.exports!.get(InternalSymbolName.ExportEquals);
|
||||
if (exported) {
|
||||
for (const decl of exported.declarations) {
|
||||
const sourceFile = decl.getSourceFile();
|
||||
if (sourceFilesSet.has(sourceFile.fileName)) {
|
||||
// At `module.exports = ...`, reference node is `module`
|
||||
const node = isBinaryExpression(decl) && isPropertyAccessExpression(decl.left)
|
||||
? decl.left.expression
|
||||
: isExportAssignment(decl)
|
||||
? Debug.assertDefined(findChildOfKind(decl, SyntaxKind.ExportKeyword, sourceFile))
|
||||
: getNameOfDeclaration(decl) || decl;
|
||||
references.push(nodeEntry(node));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return references.length ? [{ definition: { type: DefinitionKind.Symbol, symbol }, references }] : emptyArray;
|
||||
}
|
||||
|
||||
@@ -831,7 +859,9 @@ namespace ts.FindAllReferences.Core {
|
||||
}
|
||||
|
||||
export function eachSymbolReferenceInFile<T>(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile, cb: (token: Identifier) => T): T | undefined {
|
||||
const symbol = checker.getSymbolAtLocation(definition);
|
||||
const symbol = isParameterPropertyDeclaration(definition.parent)
|
||||
? first(checker.getSymbolsOfParameterPropertyDeclaration(definition.parent, definition.text))
|
||||
: checker.getSymbolAtLocation(definition);
|
||||
if (!symbol) return undefined;
|
||||
for (const token of getPossibleSymbolReferenceNodes(sourceFile, symbol.name)) {
|
||||
if (!isIdentifier(token) || token === definition || token.escapedText !== definition.escapedText) continue;
|
||||
@@ -920,7 +950,7 @@ namespace ts.FindAllReferences.Core {
|
||||
|
||||
case SyntaxKind.StringLiteral: {
|
||||
const str = node as StringLiteral;
|
||||
return (isLiteralNameOfPropertyDeclarationOrIndexAccess(str) || isNameOfModuleDeclaration(node) || isExpressionOfExternalModuleImportEqualsDeclaration(node)) &&
|
||||
return (isLiteralNameOfPropertyDeclarationOrIndexAccess(str) || isNameOfModuleDeclaration(node) || isExpressionOfExternalModuleImportEqualsDeclaration(node) || (isCallExpression(node.parent) && isBindableObjectDefinePropertyCall(node.parent) && node.parent.arguments[1] === node)) &&
|
||||
str.text.length === searchSymbolName.length;
|
||||
}
|
||||
|
||||
|
||||
@@ -334,7 +334,6 @@ namespace ts.formatting {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function formatNodeGivenIndentation(node: Node, sourceFileLike: SourceFileLike, languageVariant: LanguageVariant, initialIndentation: number, delta: number, formatContext: FormatContext): TextChange[] {
|
||||
const range = { pos: 0, end: sourceFileLike.text.length };
|
||||
return getFormattingScanner(sourceFileLike.text, languageVariant, range.pos, range.end, scanner => formatSpanWorker(
|
||||
@@ -365,16 +364,21 @@ namespace ts.formatting {
|
||||
function formatSpan(originalRange: TextRange, sourceFile: SourceFile, formatContext: FormatContext, requestKind: FormattingRequestKind): TextChange[] {
|
||||
// find the smallest node that fully wraps the range and compute the initial indentation for the node
|
||||
const enclosingNode = findEnclosingNode(originalRange, sourceFile);
|
||||
return getFormattingScanner(sourceFile.text, sourceFile.languageVariant, getScanStartPosition(enclosingNode, originalRange, sourceFile), originalRange.end, scanner => formatSpanWorker(
|
||||
originalRange,
|
||||
enclosingNode,
|
||||
SmartIndenter.getIndentationForNode(enclosingNode, originalRange, sourceFile, formatContext.options),
|
||||
getOwnOrInheritedDelta(enclosingNode, formatContext.options, sourceFile),
|
||||
scanner,
|
||||
formatContext,
|
||||
requestKind,
|
||||
prepareRangeContainsErrorFunction(sourceFile.parseDiagnostics, originalRange),
|
||||
sourceFile));
|
||||
return getFormattingScanner(
|
||||
sourceFile.text,
|
||||
sourceFile.languageVariant,
|
||||
getScanStartPosition(enclosingNode, originalRange, sourceFile),
|
||||
originalRange.end,
|
||||
scanner => formatSpanWorker(
|
||||
originalRange,
|
||||
enclosingNode,
|
||||
SmartIndenter.getIndentationForNode(enclosingNode, originalRange, sourceFile, formatContext.options),
|
||||
getOwnOrInheritedDelta(enclosingNode, formatContext.options, sourceFile),
|
||||
scanner,
|
||||
formatContext,
|
||||
requestKind,
|
||||
prepareRangeContainsErrorFunction(sourceFile.parseDiagnostics, originalRange),
|
||||
sourceFile));
|
||||
}
|
||||
|
||||
function formatSpanWorker(originalRange: TextRange,
|
||||
@@ -413,7 +417,8 @@ namespace ts.formatting {
|
||||
if (!formattingScanner.isOnToken()) {
|
||||
const leadingTrivia = formattingScanner.getCurrentLeadingTrivia();
|
||||
if (leadingTrivia) {
|
||||
processTrivia(leadingTrivia, enclosingNode, enclosingNode, /*dynamicIndentation*/ undefined!); // TODO: GH#18217
|
||||
indentTriviaItems(leadingTrivia, initialIndentation, /*indentNextTokenOrTrivia*/ false,
|
||||
item => processRange(item, sourceFile.getLineAndCharacterOfPosition(item.pos), enclosingNode, enclosingNode, /*dynamicIndentation*/ undefined!));
|
||||
trimTrailingWhitespacesForRemainingRange();
|
||||
}
|
||||
}
|
||||
@@ -753,17 +758,17 @@ namespace ts.formatting {
|
||||
|
||||
const listEndToken = getCloseTokenForOpenToken(listStartToken);
|
||||
if (listEndToken !== SyntaxKind.Unknown && formattingScanner.isOnToken()) {
|
||||
let tokenInfo = formattingScanner.readTokenInfo(parent);
|
||||
let tokenInfo: TokenInfo | undefined = formattingScanner.readTokenInfo(parent);
|
||||
if (tokenInfo.token.kind === SyntaxKind.CommaToken && isCallLikeExpression(parent)) {
|
||||
formattingScanner.advance();
|
||||
tokenInfo = formattingScanner.readTokenInfo(parent);
|
||||
tokenInfo = formattingScanner.isOnToken() ? formattingScanner.readTokenInfo(parent) : undefined;
|
||||
}
|
||||
|
||||
// consume the list end token only if it is still belong to the parent
|
||||
// there might be the case when current token matches end token but does not considered as one
|
||||
// function (x: function) <--
|
||||
// without this check close paren will be interpreted as list end token for function expression which is wrong
|
||||
if (tokenInfo.token.kind === listEndToken && rangeContainsRange(parent, tokenInfo.token)) {
|
||||
if (tokenInfo && tokenInfo.token.kind === listEndToken && rangeContainsRange(parent, tokenInfo.token)) {
|
||||
// consume list end token
|
||||
consumeTokenAndAdvanceScanner(tokenInfo, parent, listDynamicIndentation, parent);
|
||||
}
|
||||
@@ -814,27 +819,8 @@ namespace ts.formatting {
|
||||
let indentNextTokenOrTrivia = true;
|
||||
if (currentTokenInfo.leadingTrivia) {
|
||||
const commentIndentation = dynamicIndentation.getIndentationForComment(currentTokenInfo.token.kind, tokenIndentation, container);
|
||||
|
||||
for (const triviaItem of currentTokenInfo.leadingTrivia) {
|
||||
const triviaInRange = rangeContainsRange(originalRange, triviaItem);
|
||||
switch (triviaItem.kind) {
|
||||
case SyntaxKind.MultiLineCommentTrivia:
|
||||
if (triviaInRange) {
|
||||
indentMultilineCommentOrJsxText(triviaItem, commentIndentation, /*firstLineIsIndented*/ !indentNextTokenOrTrivia);
|
||||
}
|
||||
indentNextTokenOrTrivia = false;
|
||||
break;
|
||||
case SyntaxKind.SingleLineCommentTrivia:
|
||||
if (indentNextTokenOrTrivia && triviaInRange) {
|
||||
insertIndentation(triviaItem.pos, commentIndentation, /*lineAdded*/ false);
|
||||
}
|
||||
indentNextTokenOrTrivia = false;
|
||||
break;
|
||||
case SyntaxKind.NewLineTrivia:
|
||||
indentNextTokenOrTrivia = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
indentNextTokenOrTrivia = indentTriviaItems(currentTokenInfo.leadingTrivia, commentIndentation, indentNextTokenOrTrivia,
|
||||
item => insertIndentation(item.pos, commentIndentation, /*lineAdded*/ false));
|
||||
}
|
||||
|
||||
// indent token only if is it is in target range and does not overlap with any error ranges
|
||||
@@ -852,6 +838,34 @@ namespace ts.formatting {
|
||||
}
|
||||
}
|
||||
|
||||
function indentTriviaItems(
|
||||
trivia: TextRangeWithKind[],
|
||||
commentIndentation: number,
|
||||
indentNextTokenOrTrivia: boolean,
|
||||
indentSingleLine: (item: TextRangeWithKind) => void) {
|
||||
for (const triviaItem of trivia) {
|
||||
const triviaInRange = rangeContainsRange(originalRange, triviaItem);
|
||||
switch (triviaItem.kind) {
|
||||
case SyntaxKind.MultiLineCommentTrivia:
|
||||
if (triviaInRange) {
|
||||
indentMultilineCommentOrJsxText(triviaItem, commentIndentation, /*firstLineIsIndented*/ !indentNextTokenOrTrivia);
|
||||
}
|
||||
indentNextTokenOrTrivia = false;
|
||||
break;
|
||||
case SyntaxKind.SingleLineCommentTrivia:
|
||||
if (indentNextTokenOrTrivia && triviaInRange) {
|
||||
indentSingleLine(triviaItem);
|
||||
}
|
||||
indentNextTokenOrTrivia = false;
|
||||
break;
|
||||
case SyntaxKind.NewLineTrivia:
|
||||
indentNextTokenOrTrivia = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return indentNextTokenOrTrivia;
|
||||
}
|
||||
|
||||
function processTrivia(trivia: TextRangeWithKind[], parent: Node, contextNode: Node, dynamicIndentation: DynamicIndentation): void {
|
||||
for (const triviaItem of trivia) {
|
||||
if (isComment(triviaItem.kind) && rangeContainsRange(originalRange, triviaItem)) {
|
||||
@@ -861,7 +875,6 @@ namespace ts.formatting {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: GH#18217 use an enum instead of `boolean | undefined`
|
||||
function processRange(range: TextRangeWithKind,
|
||||
rangeStart: LineAndCharacter,
|
||||
parent: Node,
|
||||
@@ -1072,7 +1085,10 @@ namespace ts.formatting {
|
||||
* Trimming will be done for lines after the previous range
|
||||
*/
|
||||
function trimTrailingWhitespacesForRemainingRange() {
|
||||
const startPosition = previousRange ? previousRange.end : originalRange.pos;
|
||||
if (!previousRange) {
|
||||
return;
|
||||
}
|
||||
const startPosition = previousRange.end;
|
||||
|
||||
const startLine = sourceFile.getLineAndCharacterOfPosition(startPosition).line;
|
||||
const endLine = sourceFile.getLineAndCharacterOfPosition(originalRange.end).line;
|
||||
|
||||
@@ -22,8 +22,8 @@ namespace ts.formatting {
|
||||
const binaryKeywordOperators = [SyntaxKind.InKeyword, SyntaxKind.InstanceOfKeyword, SyntaxKind.OfKeyword, SyntaxKind.AsKeyword, SyntaxKind.IsKeyword];
|
||||
const unaryPrefixOperators = [SyntaxKind.PlusPlusToken, SyntaxKind.MinusMinusToken, SyntaxKind.TildeToken, SyntaxKind.ExclamationToken];
|
||||
const unaryPrefixExpressions = [
|
||||
SyntaxKind.NumericLiteral, SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.OpenBracketToken,
|
||||
SyntaxKind.OpenBraceToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword];
|
||||
SyntaxKind.NumericLiteral, SyntaxKind.BigIntLiteral, SyntaxKind.Identifier, SyntaxKind.OpenParenToken,
|
||||
SyntaxKind.OpenBracketToken, SyntaxKind.OpenBraceToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword];
|
||||
const unaryPreincrementExpressions = [SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword];
|
||||
const unaryPostincrementExpressions = [SyntaxKind.Identifier, SyntaxKind.CloseParenToken, SyntaxKind.CloseBracketToken, SyntaxKind.NewKeyword];
|
||||
const unaryPredecrementExpressions = [SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword];
|
||||
|
||||
@@ -79,14 +79,14 @@ namespace ts.formatting {
|
||||
return findFirstNonWhitespaceColumn(getStartPositionOfLine(commentStartLine, sourceFile), position, sourceFile, options);
|
||||
}
|
||||
|
||||
const startPostionOfLine = getStartPositionOfLine(previousLine, sourceFile);
|
||||
const { column, character } = findFirstNonWhitespaceCharacterAndColumn(startPostionOfLine, position, sourceFile, options);
|
||||
const startPositionOfLine = getStartPositionOfLine(previousLine, sourceFile);
|
||||
const { column, character } = findFirstNonWhitespaceCharacterAndColumn(startPositionOfLine, position, sourceFile, options);
|
||||
|
||||
if (column === 0) {
|
||||
return column;
|
||||
}
|
||||
|
||||
const firstNonWhitespaceCharacterCode = sourceFile.text.charCodeAt(startPostionOfLine + character);
|
||||
const firstNonWhitespaceCharacterCode = sourceFile.text.charCodeAt(startPositionOfLine + character);
|
||||
return firstNonWhitespaceCharacterCode === CharacterCodes.asterisk ? column - 1 : column;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace ts {
|
||||
const oldToNew = getPathUpdater(oldFileOrDirPath, newFileOrDirPath, getCanonicalFileName, sourceMapper);
|
||||
const newToOld = getPathUpdater(newFileOrDirPath, oldFileOrDirPath, getCanonicalFileName, sourceMapper);
|
||||
return textChanges.ChangeTracker.with({ host, formatContext }, changeTracker => {
|
||||
updateTsconfigFiles(program, changeTracker, oldToNew, newFileOrDirPath, host.getCurrentDirectory(), useCaseSensitiveFileNames);
|
||||
updateTsconfigFiles(program, changeTracker, oldToNew, oldFileOrDirPath, newFileOrDirPath, host.getCurrentDirectory(), useCaseSensitiveFileNames);
|
||||
updateImports(program, changeTracker, oldToNew, newToOld, host, getCanonicalFileName);
|
||||
});
|
||||
}
|
||||
@@ -45,7 +45,7 @@ namespace ts {
|
||||
return combinePathsSafe(getDirectoryPath(a1), rel);
|
||||
}
|
||||
|
||||
function updateTsconfigFiles(program: Program, changeTracker: textChanges.ChangeTracker, oldToNew: PathUpdater, newFileOrDirPath: string, currentDirectory: string, useCaseSensitiveFileNames: boolean): void {
|
||||
function updateTsconfigFiles(program: Program, changeTracker: textChanges.ChangeTracker, oldToNew: PathUpdater, oldFileOrDirPath: string, newFileOrDirPath: string, currentDirectory: string, useCaseSensitiveFileNames: boolean): void {
|
||||
const { configFile } = program.getCompilerOptions();
|
||||
if (!configFile) return;
|
||||
const configDir = getDirectoryPath(configFile.fileName);
|
||||
@@ -63,7 +63,8 @@ namespace ts {
|
||||
const includes = mapDefined(property.initializer.elements, e => isStringLiteral(e) ? e.text : undefined);
|
||||
const matchers = getFileMatcherPatterns(configDir, /*excludes*/ [], includes, useCaseSensitiveFileNames, currentDirectory);
|
||||
// If there isn't some include for this, add a new one.
|
||||
if (!getRegexFromPattern(Debug.assertDefined(matchers.includeFilePattern), useCaseSensitiveFileNames).test(newFileOrDirPath)) {
|
||||
if (getRegexFromPattern(Debug.assertDefined(matchers.includeFilePattern), useCaseSensitiveFileNames).test(oldFileOrDirPath) &&
|
||||
!getRegexFromPattern(Debug.assertDefined(matchers.includeFilePattern), useCaseSensitiveFileNames).test(newFileOrDirPath)) {
|
||||
changeTracker.insertNodeAfter(configFile, last(property.initializer.elements), createStringLiteral(relativePath(newFileOrDirPath)));
|
||||
}
|
||||
}
|
||||
@@ -72,7 +73,7 @@ namespace ts {
|
||||
case "compilerOptions":
|
||||
forEachProperty(property.initializer, (property, propertyName) => {
|
||||
const option = getOptionFromName(propertyName);
|
||||
if (option && (option.isFilePath || option.type === "list" && (option as CommandLineOptionOfListType).element.isFilePath)) {
|
||||
if (option && (option.isFilePath || option.type === "list" && option.element.isFilePath)) {
|
||||
updatePaths(property);
|
||||
}
|
||||
else if (propertyName === "paths") {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* @internal */
|
||||
namespace ts.GoToDefinition {
|
||||
export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number): DefinitionInfo[] | undefined {
|
||||
export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number): ReadonlyArray<DefinitionInfo> | undefined {
|
||||
const reference = getReferenceAtPosition(sourceFile, position, program);
|
||||
if (reference) {
|
||||
return [getDefinitionInfoForFileReference(reference.fileName, reference.file.fileName)];
|
||||
@@ -129,7 +129,7 @@ namespace ts.GoToDefinition {
|
||||
}
|
||||
|
||||
/// Goto type
|
||||
export function getTypeDefinitionAtPosition(typeChecker: TypeChecker, sourceFile: SourceFile, position: number): DefinitionInfo[] | undefined {
|
||||
export function getTypeDefinitionAtPosition(typeChecker: TypeChecker, sourceFile: SourceFile, position: number): ReadonlyArray<DefinitionInfo> | undefined {
|
||||
const node = getTouchingPropertyName(sourceFile, position);
|
||||
if (node === sourceFile) {
|
||||
return undefined;
|
||||
@@ -145,7 +145,7 @@ namespace ts.GoToDefinition {
|
||||
return fromReturnType && fromReturnType.length !== 0 ? fromReturnType : definitionFromType(typeAtLocation, typeChecker, node);
|
||||
}
|
||||
|
||||
function definitionFromType(type: Type, checker: TypeChecker, node: Node): DefinitionInfo[] {
|
||||
function definitionFromType(type: Type, checker: TypeChecker, node: Node): ReadonlyArray<DefinitionInfo> {
|
||||
return flatMap(type.isUnion() && !(type.flags & TypeFlags.Enum) ? type.types : [type], t =>
|
||||
t.symbol && getDefinitionFromSymbol(checker, t.symbol, node));
|
||||
}
|
||||
|
||||
@@ -281,6 +281,9 @@ namespace ts.NavigationBar {
|
||||
case AssignmentDeclarationKind.ThisProperty:
|
||||
case AssignmentDeclarationKind.Property:
|
||||
case AssignmentDeclarationKind.None:
|
||||
case AssignmentDeclarationKind.ObjectDefinePropertyValue:
|
||||
case AssignmentDeclarationKind.ObjectDefinePropertyExports:
|
||||
case AssignmentDeclarationKind.ObjectDefinePrototypeProperty:
|
||||
break;
|
||||
default:
|
||||
Debug.assertNever(special);
|
||||
|
||||
@@ -146,7 +146,7 @@ namespace ts.OrganizeImports {
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/* @internal */ // Internal for testing
|
||||
// Internal for testing
|
||||
/**
|
||||
* @param importGroup a list of ImportDeclarations, all with the same module name.
|
||||
*/
|
||||
@@ -266,7 +266,7 @@ namespace ts.OrganizeImports {
|
||||
}
|
||||
}
|
||||
|
||||
/* @internal */ // Internal for testing
|
||||
// Internal for testing
|
||||
/**
|
||||
* @param exportGroup a list of ExportDeclarations, all with the same module name.
|
||||
*/
|
||||
|
||||
@@ -1,512 +0,0 @@
|
||||
/* @internal */
|
||||
namespace ts.Completions.PathCompletions {
|
||||
export interface NameAndKind {
|
||||
readonly name: string;
|
||||
readonly kind: ScriptElementKind.scriptElement | ScriptElementKind.directory | ScriptElementKind.externalModuleName;
|
||||
}
|
||||
export interface PathCompletion extends NameAndKind {
|
||||
readonly span: TextSpan | undefined;
|
||||
}
|
||||
|
||||
function nameAndKind(name: string, kind: NameAndKind["kind"]): NameAndKind {
|
||||
return { name, kind };
|
||||
}
|
||||
function addReplacementSpans(text: string, textStart: number, names: ReadonlyArray<NameAndKind>): ReadonlyArray<PathCompletion> {
|
||||
const span = getDirectoryFragmentTextSpan(text, textStart);
|
||||
return names.map(({ name, kind }): PathCompletion => ({ name, kind, span }));
|
||||
}
|
||||
|
||||
export function getStringLiteralCompletionsFromModuleNames(sourceFile: SourceFile, node: LiteralExpression, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): ReadonlyArray<PathCompletion> {
|
||||
return addReplacementSpans(node.text, node.getStart(sourceFile) + 1, getStringLiteralCompletionsFromModuleNamesWorker(sourceFile, node, compilerOptions, host, typeChecker));
|
||||
}
|
||||
|
||||
function getStringLiteralCompletionsFromModuleNamesWorker(sourceFile: SourceFile, node: LiteralExpression, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): ReadonlyArray<NameAndKind> {
|
||||
const literalValue = normalizeSlashes(node.text);
|
||||
|
||||
const scriptPath = sourceFile.path;
|
||||
const scriptDirectory = getDirectoryPath(scriptPath);
|
||||
|
||||
if (isPathRelativeToScript(literalValue) || isRootedDiskPath(literalValue)) {
|
||||
const extensions = getSupportedExtensionsForModuleResolution(compilerOptions);
|
||||
if (compilerOptions.rootDirs) {
|
||||
return getCompletionEntriesForDirectoryFragmentWithRootDirs(
|
||||
compilerOptions.rootDirs, literalValue, scriptDirectory, extensions, /*includeExtensions*/ false, compilerOptions, host, scriptPath);
|
||||
}
|
||||
else {
|
||||
return getCompletionEntriesForDirectoryFragment(literalValue, scriptDirectory, extensions, /*includeExtensions*/ false, host, scriptPath);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, compilerOptions, host, typeChecker);
|
||||
}
|
||||
}
|
||||
|
||||
function getSupportedExtensionsForModuleResolution(compilerOptions: CompilerOptions) {
|
||||
const extensions = getSupportedExtensions(compilerOptions);
|
||||
return compilerOptions.resolveJsonModule && getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs ?
|
||||
extensions.concat(Extension.Json) :
|
||||
extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a script path and returns paths for all potential folders that could be merged with its
|
||||
* containing folder via the "rootDirs" compiler option
|
||||
*/
|
||||
function getBaseDirectoriesFromRootDirs(rootDirs: string[], basePath: string, scriptPath: string, ignoreCase: boolean): string[] {
|
||||
// Make all paths absolute/normalized if they are not already
|
||||
rootDirs = rootDirs.map(rootDirectory => normalizePath(isRootedDiskPath(rootDirectory) ? rootDirectory : combinePaths(basePath, rootDirectory)));
|
||||
|
||||
// Determine the path to the directory containing the script relative to the root directory it is contained within
|
||||
const relativeDirectory = firstDefined(rootDirs, rootDirectory =>
|
||||
containsPath(rootDirectory, scriptPath, basePath, ignoreCase) ? scriptPath.substr(rootDirectory.length) : undefined)!; // TODO: GH#18217
|
||||
|
||||
// Now find a path for each potential directory that is to be merged with the one containing the script
|
||||
return deduplicate<string>(
|
||||
rootDirs.map(rootDirectory => combinePaths(rootDirectory, relativeDirectory)),
|
||||
equateStringsCaseSensitive,
|
||||
compareStringsCaseSensitive);
|
||||
}
|
||||
|
||||
function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: ReadonlyArray<string>, includeExtensions: boolean, compilerOptions: CompilerOptions, host: LanguageServiceHost, exclude?: string): NameAndKind[] {
|
||||
const basePath = compilerOptions.project || host.getCurrentDirectory();
|
||||
const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames());
|
||||
const baseDirectories = getBaseDirectoriesFromRootDirs(rootDirs, basePath, scriptPath, ignoreCase);
|
||||
|
||||
const result: NameAndKind[] = [];
|
||||
|
||||
for (const baseDirectory of baseDirectories) {
|
||||
getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensions, includeExtensions, host, exclude, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a path ending at a directory, gets the completions for the path, and filters for those entries containing the basename.
|
||||
*/
|
||||
function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: ReadonlyArray<string>, includeExtensions: boolean, host: LanguageServiceHost, exclude?: string, result: NameAndKind[] = []): NameAndKind[] {
|
||||
if (fragment === undefined) {
|
||||
fragment = "";
|
||||
}
|
||||
|
||||
fragment = normalizeSlashes(fragment);
|
||||
|
||||
/**
|
||||
* Remove the basename from the path. Note that we don't use the basename to filter completions;
|
||||
* the client is responsible for refining completions.
|
||||
*/
|
||||
if (!hasTrailingDirectorySeparator(fragment)) {
|
||||
fragment = getDirectoryPath(fragment);
|
||||
}
|
||||
|
||||
if (fragment === "") {
|
||||
fragment = "." + directorySeparator;
|
||||
}
|
||||
|
||||
fragment = ensureTrailingDirectorySeparator(fragment);
|
||||
|
||||
// const absolutePath = normalizeAndPreserveTrailingSlash(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment)); // TODO(rbuckton): should use resolvePaths
|
||||
const absolutePath = resolvePath(scriptPath, fragment);
|
||||
const baseDirectory = hasTrailingDirectorySeparator(absolutePath) ? absolutePath : getDirectoryPath(absolutePath);
|
||||
const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames());
|
||||
|
||||
if (tryDirectoryExists(host, baseDirectory)) {
|
||||
// Enumerate the available files if possible
|
||||
const files = tryReadDirectory(host, baseDirectory, extensions, /*exclude*/ undefined, /*include*/ ["./*"]);
|
||||
|
||||
if (files) {
|
||||
/**
|
||||
* Multiple file entries might map to the same truncated name once we remove extensions
|
||||
* (happens iff includeExtensions === false)so we use a set-like data structure. Eg:
|
||||
*
|
||||
* both foo.ts and foo.tsx become foo
|
||||
*/
|
||||
const foundFiles = createMap<true>();
|
||||
for (let filePath of files) {
|
||||
filePath = normalizePath(filePath);
|
||||
if (exclude && comparePaths(filePath, exclude, scriptPath, ignoreCase) === Comparison.EqualTo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const foundFileName = includeExtensions || fileExtensionIs(filePath, Extension.Json) ? getBaseFileName(filePath) : removeFileExtension(getBaseFileName(filePath));
|
||||
|
||||
if (!foundFiles.has(foundFileName)) {
|
||||
foundFiles.set(foundFileName, true);
|
||||
}
|
||||
}
|
||||
|
||||
forEachKey(foundFiles, foundFile => {
|
||||
result.push(nameAndKind(foundFile, ScriptElementKind.scriptElement));
|
||||
});
|
||||
}
|
||||
|
||||
// If possible, get folder completion as well
|
||||
const directories = tryGetDirectories(host, baseDirectory);
|
||||
|
||||
if (directories) {
|
||||
for (const directory of directories) {
|
||||
const directoryName = getBaseFileName(normalizePath(directory));
|
||||
if (directoryName !== "@types") {
|
||||
result.push(nameAndKind(directoryName, ScriptElementKind.directory));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for a version redirect
|
||||
const packageJsonPath = findPackageJson(baseDirectory, host);
|
||||
if (packageJsonPath) {
|
||||
const packageJson = readJson(packageJsonPath, host as { readFile: (filename: string) => string | undefined });
|
||||
const typesVersions = (packageJson as any).typesVersions;
|
||||
if (typeof typesVersions === "object") {
|
||||
const versionResult = getPackageJsonTypesVersionsPaths(typesVersions);
|
||||
const versionPaths = versionResult && versionResult.paths;
|
||||
const rest = absolutePath.slice(ensureTrailingDirectorySeparator(baseDirectory).length);
|
||||
if (versionPaths) {
|
||||
addCompletionEntriesFromPaths(result, rest, baseDirectory, extensions, versionPaths, host);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function addCompletionEntriesFromPaths(result: NameAndKind[], fragment: string, baseDirectory: string, fileExtensions: ReadonlyArray<string>, paths: MapLike<string[]>, host: LanguageServiceHost) {
|
||||
for (const path in paths) {
|
||||
if (!hasProperty(paths, path)) continue;
|
||||
const patterns = paths[path];
|
||||
if (patterns) {
|
||||
for (const { name, kind } of getCompletionsForPathMapping(path, patterns, fragment, baseDirectory, fileExtensions, host)) {
|
||||
// Path mappings may provide a duplicate way to get to something we've already added, so don't add again.
|
||||
if (!result.some(entry => entry.name === name)) {
|
||||
result.push(nameAndKind(name, kind));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check all of the declared modules and those in node modules. Possible sources of modules:
|
||||
* Modules that are found by the type checker
|
||||
* Modules found relative to "baseUrl" compliler options (including patterns from "paths" compiler option)
|
||||
* Modules from node_modules (i.e. those listed in package.json)
|
||||
* This includes all files that are found in node_modules/moduleName/ with acceptable file extensions
|
||||
*/
|
||||
function getCompletionEntriesForNonRelativeModules(fragment: string, scriptPath: string, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): NameAndKind[] {
|
||||
const { baseUrl, paths } = compilerOptions;
|
||||
|
||||
const result: NameAndKind[] = [];
|
||||
|
||||
const fileExtensions = getSupportedExtensionsForModuleResolution(compilerOptions);
|
||||
if (baseUrl) {
|
||||
const projectDir = compilerOptions.project || host.getCurrentDirectory();
|
||||
const absolute = normalizePath(combinePaths(projectDir, baseUrl));
|
||||
getCompletionEntriesForDirectoryFragment(fragment, absolute, fileExtensions, /*includeExtensions*/ false, host, /*exclude*/ undefined, result);
|
||||
if (paths) {
|
||||
addCompletionEntriesFromPaths(result, fragment, absolute, fileExtensions, paths, host);
|
||||
}
|
||||
}
|
||||
|
||||
const fragmentDirectory = containsSlash(fragment) ? hasTrailingDirectorySeparator(fragment) ? fragment : getDirectoryPath(fragment) : undefined;
|
||||
for (const ambientName of getAmbientModuleCompletions(fragment, fragmentDirectory, typeChecker)) {
|
||||
result.push(nameAndKind(ambientName, ScriptElementKind.externalModuleName));
|
||||
}
|
||||
|
||||
getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, result);
|
||||
|
||||
if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs) {
|
||||
// If looking for a global package name, don't just include everything in `node_modules` because that includes dependencies' own dependencies.
|
||||
// (But do if we didn't find anything, e.g. 'package.json' missing.)
|
||||
let foundGlobal = false;
|
||||
if (fragmentDirectory === undefined) {
|
||||
for (const moduleName of enumerateNodeModulesVisibleToScript(host, scriptPath)) {
|
||||
if (!result.some(entry => entry.name === moduleName)) {
|
||||
foundGlobal = true;
|
||||
result.push(nameAndKind(moduleName, ScriptElementKind.externalModuleName));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!foundGlobal) {
|
||||
forEachAncestorDirectory(scriptPath, ancestor => {
|
||||
const nodeModules = combinePaths(ancestor, "node_modules");
|
||||
if (tryDirectoryExists(host, nodeModules)) {
|
||||
getCompletionEntriesForDirectoryFragment(fragment, nodeModules, fileExtensions, /*includeExtensions*/ false, host, /*exclude*/ undefined, result);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function getCompletionsForPathMapping(
|
||||
path: string, patterns: ReadonlyArray<string>, fragment: string, baseUrl: string, fileExtensions: ReadonlyArray<string>, host: LanguageServiceHost,
|
||||
): ReadonlyArray<NameAndKind> {
|
||||
if (!endsWith(path, "*")) {
|
||||
// For a path mapping "foo": ["/x/y/z.ts"], add "foo" itself as a completion.
|
||||
return !stringContains(path, "*") ? justPathMappingName(path) : emptyArray;
|
||||
}
|
||||
|
||||
const pathPrefix = path.slice(0, path.length - 1);
|
||||
const remainingFragment = tryRemovePrefix(fragment, pathPrefix);
|
||||
return remainingFragment === undefined ? justPathMappingName(pathPrefix) : flatMap(patterns, pattern =>
|
||||
getModulesForPathsPattern(remainingFragment, baseUrl, pattern, fileExtensions, host));
|
||||
|
||||
function justPathMappingName(name: string): ReadonlyArray<NameAndKind> {
|
||||
return startsWith(name, fragment) ? [{ name, kind: ScriptElementKind.directory }] : emptyArray;
|
||||
}
|
||||
}
|
||||
|
||||
function getModulesForPathsPattern(fragment: string, baseUrl: string, pattern: string, fileExtensions: ReadonlyArray<string>, host: LanguageServiceHost): ReadonlyArray<NameAndKind> | undefined {
|
||||
if (!host.readDirectory) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const parsed = hasZeroOrOneAsteriskCharacter(pattern) ? tryParsePattern(pattern) : undefined;
|
||||
if (!parsed) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// The prefix has two effective parts: the directory path and the base component after the filepath that is not a
|
||||
// full directory component. For example: directory/path/of/prefix/base*
|
||||
const normalizedPrefix = resolvePath(parsed.prefix);
|
||||
const normalizedPrefixDirectory = hasTrailingDirectorySeparator(parsed.prefix) ? normalizedPrefix : getDirectoryPath(normalizedPrefix);
|
||||
const normalizedPrefixBase = hasTrailingDirectorySeparator(parsed.prefix) ? "" : getBaseFileName(normalizedPrefix);
|
||||
|
||||
const fragmentHasPath = containsSlash(fragment);
|
||||
const fragmentDirectory = fragmentHasPath ? hasTrailingDirectorySeparator(fragment) ? fragment : getDirectoryPath(fragment) : undefined;
|
||||
|
||||
// Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call
|
||||
const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + fragmentDirectory) : normalizedPrefixDirectory;
|
||||
|
||||
const normalizedSuffix = normalizePath(parsed.suffix);
|
||||
// Need to normalize after combining: If we combinePaths("a", "../b"), we want "b" and not "a/../b".
|
||||
const baseDirectory = normalizePath(combinePaths(baseUrl, expandedPrefixDirectory));
|
||||
const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator(baseDirectory) + normalizedPrefixBase;
|
||||
|
||||
// If we have a suffix, then we need to read the directory all the way down. We could create a glob
|
||||
// that encodes the suffix, but we would have to escape the character "?" which readDirectory
|
||||
// doesn't support. For now, this is safer but slower
|
||||
const includeGlob = normalizedSuffix ? "**/*" : "./*";
|
||||
|
||||
const matches = tryReadDirectory(host, baseDirectory, fileExtensions, /*exclude*/ undefined, [includeGlob]).map<NameAndKind>(name => ({ name, kind: ScriptElementKind.scriptElement }));
|
||||
const directories = tryGetDirectories(host, baseDirectory).map(d => combinePaths(baseDirectory, d)).map<NameAndKind>(name => ({ name, kind: ScriptElementKind.directory }));
|
||||
|
||||
// Trim away prefix and suffix
|
||||
return mapDefined<NameAndKind, NameAndKind>(concatenate(matches, directories), ({ name, kind }) => {
|
||||
const normalizedMatch = normalizePath(name);
|
||||
const inner = withoutStartAndEnd(normalizedMatch, completePrefix, normalizedSuffix);
|
||||
return inner !== undefined ? { name: removeLeadingDirectorySeparator(removeFileExtension(inner)), kind } : undefined;
|
||||
});
|
||||
}
|
||||
|
||||
function withoutStartAndEnd(s: string, start: string, end: string): string | undefined {
|
||||
return startsWith(s, start) && endsWith(s, end) ? s.slice(start.length, s.length - end.length) : undefined;
|
||||
}
|
||||
|
||||
function removeLeadingDirectorySeparator(path: string): string {
|
||||
return path[0] === directorySeparator ? path.slice(1) : path;
|
||||
}
|
||||
|
||||
function getAmbientModuleCompletions(fragment: string, fragmentDirectory: string | undefined, checker: TypeChecker): ReadonlyArray<string> {
|
||||
// Get modules that the type checker picked up
|
||||
const ambientModules = checker.getAmbientModules().map(sym => stripQuotes(sym.name));
|
||||
const nonRelativeModuleNames = ambientModules.filter(moduleName => startsWith(moduleName, fragment));
|
||||
|
||||
// Nested modules of the form "module-name/sub" need to be adjusted to only return the string
|
||||
// after the last '/' that appears in the fragment because that's where the replacement span
|
||||
// starts
|
||||
if (fragmentDirectory !== undefined) {
|
||||
const moduleNameWithSeparator = ensureTrailingDirectorySeparator(fragmentDirectory);
|
||||
return nonRelativeModuleNames.map(nonRelativeModuleName => removePrefix(nonRelativeModuleName, moduleNameWithSeparator));
|
||||
}
|
||||
return nonRelativeModuleNames;
|
||||
}
|
||||
|
||||
export function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): ReadonlyArray<PathCompletion> | undefined {
|
||||
const token = getTokenAtPosition(sourceFile, position);
|
||||
const commentRanges = getLeadingCommentRanges(sourceFile.text, token.pos);
|
||||
const range = commentRanges && find(commentRanges, commentRange => position >= commentRange.pos && position <= commentRange.end);
|
||||
if (!range) {
|
||||
return undefined;
|
||||
}
|
||||
const text = sourceFile.text.slice(range.pos, position);
|
||||
const match = tripleSlashDirectiveFragmentRegex.exec(text);
|
||||
if (!match) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const [, prefix, kind, toComplete] = match;
|
||||
const scriptPath = getDirectoryPath(sourceFile.path);
|
||||
const names = kind === "path" ? getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(compilerOptions), /*includeExtensions*/ true, host, sourceFile.path)
|
||||
: kind === "types" ? getCompletionEntriesFromTypings(host, compilerOptions, scriptPath)
|
||||
: undefined;
|
||||
return names && addReplacementSpans(toComplete, range.pos + prefix.length, names);
|
||||
}
|
||||
|
||||
function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, result: NameAndKind[] = []): NameAndKind[] {
|
||||
// Check for typings specified in compiler options
|
||||
const seen = createMap<true>();
|
||||
if (options.types) {
|
||||
for (const typesName of options.types) {
|
||||
const moduleName = unmangleScopedPackageName(typesName);
|
||||
pushResult(moduleName);
|
||||
}
|
||||
}
|
||||
else if (host.getDirectories) {
|
||||
let typeRoots: ReadonlyArray<string> | undefined;
|
||||
try {
|
||||
typeRoots = getEffectiveTypeRoots(options, host);
|
||||
}
|
||||
catch { /* Wrap in try catch because getEffectiveTypeRoots touches the filesystem */ }
|
||||
|
||||
if (typeRoots) {
|
||||
for (const root of typeRoots) {
|
||||
getCompletionEntriesFromDirectories(root);
|
||||
}
|
||||
}
|
||||
|
||||
// Also get all @types typings installed in visible node_modules directories
|
||||
for (const packageJson of findPackageJsons(scriptPath, host)) {
|
||||
const typesDir = combinePaths(getDirectoryPath(packageJson), "node_modules/@types");
|
||||
getCompletionEntriesFromDirectories(typesDir);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
function getCompletionEntriesFromDirectories(directory: string) {
|
||||
Debug.assert(!!host.getDirectories);
|
||||
if (tryDirectoryExists(host, directory)) {
|
||||
const directories = tryGetDirectories(host, directory);
|
||||
if (directories) {
|
||||
for (let typeDirectory of directories) {
|
||||
typeDirectory = normalizePath(typeDirectory);
|
||||
const directoryName = getBaseFileName(typeDirectory);
|
||||
const moduleName = unmangleScopedPackageName(directoryName);
|
||||
pushResult(moduleName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pushResult(moduleName: string) {
|
||||
if (!seen.has(moduleName)) {
|
||||
result.push(nameAndKind(moduleName, ScriptElementKind.externalModuleName));
|
||||
seen.set(moduleName, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findPackageJsons(directory: string, host: LanguageServiceHost): string[] {
|
||||
const paths: string[] = [];
|
||||
forEachAncestorDirectory(directory, ancestor => {
|
||||
const currentConfigPath = findConfigFile(ancestor, (f) => tryFileExists(host, f), "package.json");
|
||||
if (!currentConfigPath) {
|
||||
return true; // break out
|
||||
}
|
||||
paths.push(currentConfigPath);
|
||||
});
|
||||
return paths;
|
||||
}
|
||||
|
||||
function findPackageJson(directory: string, host: LanguageServiceHost): string | undefined {
|
||||
let packageJson: string | undefined;
|
||||
forEachAncestorDirectory(directory, ancestor => {
|
||||
if (ancestor === "node_modules") return true;
|
||||
packageJson = findConfigFile(ancestor, (f) => tryFileExists(host, f), "package.json");
|
||||
if (packageJson) {
|
||||
return true; // break out
|
||||
}
|
||||
});
|
||||
return packageJson;
|
||||
}
|
||||
|
||||
function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string): ReadonlyArray<string> {
|
||||
if (!host.readFile || !host.fileExists) return emptyArray;
|
||||
|
||||
const result: string[] = [];
|
||||
for (const packageJson of findPackageJsons(scriptPath, host)) {
|
||||
const contents = readJson(packageJson, host as { readFile: (filename: string) => string | undefined }); // Cast to assert that readFile is defined
|
||||
// Provide completions for all non @types dependencies
|
||||
for (const key of nodeModulesDependencyKeys) {
|
||||
const dependencies: object | undefined = (contents as any)[key];
|
||||
if (!dependencies) continue;
|
||||
for (const dep in dependencies) {
|
||||
if (dependencies.hasOwnProperty(dep) && !startsWith(dep, "@types/")) {
|
||||
result.push(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Replace everything after the last directory separator that appears
|
||||
function getDirectoryFragmentTextSpan(text: string, textStart: number): TextSpan | undefined {
|
||||
const index = Math.max(text.lastIndexOf(directorySeparator), text.lastIndexOf("\\"));
|
||||
const offset = index !== -1 ? index + 1 : 0;
|
||||
// If the range is an identifier, span is unnecessary.
|
||||
const length = text.length - offset;
|
||||
return length === 0 || isIdentifierText(text.substr(offset, length), ScriptTarget.ESNext) ? undefined : createTextSpan(textStart + offset, length);
|
||||
}
|
||||
|
||||
// Returns true if the path is explicitly relative to the script (i.e. relative to . or ..)
|
||||
function isPathRelativeToScript(path: string) {
|
||||
if (path && path.length >= 2 && path.charCodeAt(0) === CharacterCodes.dot) {
|
||||
const slashIndex = path.length >= 3 && path.charCodeAt(1) === CharacterCodes.dot ? 2 : 1;
|
||||
const slashCharCode = path.charCodeAt(slashIndex);
|
||||
return slashCharCode === CharacterCodes.slash || slashCharCode === CharacterCodes.backslash;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches a triple slash reference directive with an incomplete string literal for its path. Used
|
||||
* to determine if the caret is currently within the string literal and capture the literal fragment
|
||||
* for completions.
|
||||
* For example, this matches
|
||||
*
|
||||
* /// <reference path="fragment
|
||||
*
|
||||
* but not
|
||||
*
|
||||
* /// <reference path="fragment"
|
||||
*/
|
||||
const tripleSlashDirectiveFragmentRegex = /^(\/\/\/\s*<reference\s+(path|types)\s*=\s*(?:'|"))([^\3"]*)$/;
|
||||
|
||||
const nodeModulesDependencyKeys = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
|
||||
|
||||
function tryGetDirectories(host: LanguageServiceHost, directoryName: string): string[] {
|
||||
return tryIOAndConsumeErrors(host, host.getDirectories, directoryName) || [];
|
||||
}
|
||||
|
||||
function tryReadDirectory(host: LanguageServiceHost, path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>): ReadonlyArray<string> {
|
||||
return tryIOAndConsumeErrors(host, host.readDirectory, path, extensions, exclude, include) || emptyArray;
|
||||
}
|
||||
|
||||
function tryFileExists(host: LanguageServiceHost, path: string): boolean {
|
||||
return tryIOAndConsumeErrors(host, host.fileExists, path);
|
||||
}
|
||||
|
||||
function tryDirectoryExists(host: LanguageServiceHost, path: string): boolean {
|
||||
try {
|
||||
return directoryProbablyExists(path, host);
|
||||
}
|
||||
catch { /*ignore*/ }
|
||||
return false;
|
||||
}
|
||||
|
||||
function tryIOAndConsumeErrors<T>(host: LanguageServiceHost, toApply: ((...a: any[]) => T) | undefined, ...args: any[]) {
|
||||
try {
|
||||
return toApply && toApply.apply(host, args);
|
||||
}
|
||||
catch { /*ignore*/ }
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function containsSlash(fragment: string) {
|
||||
return stringContains(fragment, directorySeparator);
|
||||
}
|
||||
}
|
||||
@@ -461,11 +461,11 @@ namespace ts {
|
||||
};
|
||||
}
|
||||
|
||||
/* @internal */ export function breakIntoCharacterSpans(identifier: string): TextSpan[] {
|
||||
export function breakIntoCharacterSpans(identifier: string): TextSpan[] {
|
||||
return breakIntoSpans(identifier, /*word:*/ false);
|
||||
}
|
||||
|
||||
/* @internal */ export function breakIntoWordSpans(identifier: string): TextSpan[] {
|
||||
export function breakIntoWordSpans(identifier: string): TextSpan[] {
|
||||
return breakIntoSpans(identifier, /*word:*/ true);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace ts {
|
||||
getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined;
|
||||
|
||||
/** Compute (quickly) which actions are available here */
|
||||
getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined;
|
||||
getAvailableActions(context: RefactorContext): ReadonlyArray<ApplicableRefactorInfo>;
|
||||
}
|
||||
|
||||
export interface RefactorContext extends textChanges.TextChangesContext {
|
||||
|
||||
@@ -15,10 +15,10 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction {
|
||||
addBraces: boolean;
|
||||
}
|
||||
|
||||
function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined {
|
||||
function getAvailableActions(context: RefactorContext): ReadonlyArray<ApplicableRefactorInfo> {
|
||||
const { file, startPosition } = context;
|
||||
const info = getConvertibleArrowFunctionAtPosition(file, startPosition);
|
||||
if (!info) return undefined;
|
||||
if (!info) return emptyArray;
|
||||
|
||||
return [{
|
||||
name: refactorName,
|
||||
|
||||
@@ -4,9 +4,9 @@ namespace ts.refactor {
|
||||
const actionNameDefaultToNamed = "Convert default export to named export";
|
||||
const actionNameNamedToDefault = "Convert named export to default export";
|
||||
registerRefactor(refactorName, {
|
||||
getAvailableActions(context): ApplicableRefactorInfo[] | undefined {
|
||||
getAvailableActions(context): ReadonlyArray<ApplicableRefactorInfo> {
|
||||
const info = getInfo(context);
|
||||
if (!info) return undefined;
|
||||
if (!info) return emptyArray;
|
||||
const description = info.wasDefault ? Diagnostics.Convert_default_export_to_named_export.message : Diagnostics.Convert_named_export_to_default_export.message;
|
||||
const actionName = info.wasDefault ? actionNameDefaultToNamed : actionNameNamedToDefault;
|
||||
return [{ name: refactorName, description, actions: [{ name: actionName, description }] }];
|
||||
|
||||
@@ -4,9 +4,9 @@ namespace ts.refactor {
|
||||
const actionNameNamespaceToNamed = "Convert namespace import to named imports";
|
||||
const actionNameNamedToNamespace = "Convert named imports to namespace import";
|
||||
registerRefactor(refactorName, {
|
||||
getAvailableActions(context): ApplicableRefactorInfo[] | undefined {
|
||||
getAvailableActions(context): ReadonlyArray<ApplicableRefactorInfo> {
|
||||
const i = getImportToConvert(context);
|
||||
if (!i) return undefined;
|
||||
if (!i) return emptyArray;
|
||||
const description = i.kind === SyntaxKind.NamespaceImport ? Diagnostics.Convert_namespace_import_to_named_imports.message : Diagnostics.Convert_named_imports_to_namespace_import.message;
|
||||
const actionName = i.kind === SyntaxKind.NamespaceImport ? actionNameNamespaceToNamed : actionNameNamedToNamespace;
|
||||
return [{ name: refactorName, description, actions: [{ name: actionName, description }] }];
|
||||
|
||||
@@ -7,18 +7,18 @@ namespace ts.refactor.extractSymbol {
|
||||
* Compute the associated code actions
|
||||
* Exported for tests.
|
||||
*/
|
||||
export function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined {
|
||||
export function getAvailableActions(context: RefactorContext): ReadonlyArray<ApplicableRefactorInfo> {
|
||||
const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context));
|
||||
|
||||
const targetRange = rangeToExtract.targetRange;
|
||||
if (targetRange === undefined) {
|
||||
return undefined;
|
||||
return emptyArray;
|
||||
}
|
||||
|
||||
const extractions = getPossibleExtractions(targetRange, context);
|
||||
if (extractions === undefined) {
|
||||
// No extractions possible
|
||||
return undefined;
|
||||
return emptyArray;
|
||||
}
|
||||
|
||||
const functionActions: RefactorActionInfo[] = [];
|
||||
@@ -82,7 +82,7 @@ namespace ts.refactor.extractSymbol {
|
||||
});
|
||||
}
|
||||
|
||||
return infos.length ? infos : undefined;
|
||||
return infos.length ? infos : emptyArray;
|
||||
}
|
||||
|
||||
/* Exported for tests */
|
||||
|
||||
@@ -20,8 +20,8 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
|
||||
readonly renameAccessor: boolean;
|
||||
}
|
||||
|
||||
function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined {
|
||||
if (!getConvertibleFieldAtPosition(context)) return undefined;
|
||||
function getAvailableActions(context: RefactorContext): ReadonlyArray<ApplicableRefactorInfo> {
|
||||
if (!getConvertibleFieldAtPosition(context)) return emptyArray;
|
||||
|
||||
return [{
|
||||
name: actionName,
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
namespace ts.refactor {
|
||||
const refactorName = "Move to a new file";
|
||||
registerRefactor(refactorName, {
|
||||
getAvailableActions(context): ApplicableRefactorInfo[] | undefined {
|
||||
if (!context.preferences.allowTextChangesInNewFiles || getStatementsToMove(context) === undefined) return undefined;
|
||||
getAvailableActions(context): ReadonlyArray<ApplicableRefactorInfo> {
|
||||
if (!context.preferences.allowTextChangesInNewFiles || getStatementsToMove(context) === undefined) return emptyArray;
|
||||
const description = getLocaleSpecificMessage(Diagnostics.Move_to_a_new_file);
|
||||
return [{ name: refactorName, description, actions: [{ name: refactorName, description }] }];
|
||||
},
|
||||
@@ -612,7 +612,7 @@ namespace ts.refactor {
|
||||
| ImportEqualsDeclaration;
|
||||
type TopLevelDeclarationStatement = NonVariableTopLevelDeclaration | VariableStatement;
|
||||
interface TopLevelVariableDeclaration extends VariableDeclaration { parent: VariableDeclarationList & { parent: VariableStatement; }; }
|
||||
type TopLevelDeclaration = NonVariableTopLevelDeclaration | TopLevelVariableDeclaration;
|
||||
type TopLevelDeclaration = NonVariableTopLevelDeclaration | TopLevelVariableDeclaration | BindingElement;
|
||||
function isTopLevelDeclaration(node: Node): node is TopLevelDeclaration {
|
||||
return isNonVariableTopLevelDeclaration(node) && isSourceFile(node.parent) || isVariableDeclaration(node) && isSourceFile(node.parent.parent.parent);
|
||||
}
|
||||
@@ -653,7 +653,7 @@ namespace ts.refactor {
|
||||
return cb(statement as FunctionDeclaration | ClassDeclaration | EnumDeclaration | ModuleDeclaration | TypeAliasDeclaration | InterfaceDeclaration | ImportEqualsDeclaration);
|
||||
|
||||
case SyntaxKind.VariableStatement:
|
||||
return forEach((statement as VariableStatement).declarationList.declarations as ReadonlyArray<TopLevelVariableDeclaration>, cb);
|
||||
return firstDefined((statement as VariableStatement).declarationList.declarations, decl => forEachTopLevelDeclarationInBindingName(decl.name, cb));
|
||||
|
||||
case SyntaxKind.ExpressionStatement: {
|
||||
const { expression } = statement as ExpressionStatement;
|
||||
@@ -663,13 +663,32 @@ namespace ts.refactor {
|
||||
}
|
||||
}
|
||||
}
|
||||
function forEachTopLevelDeclarationInBindingName<T>(name: BindingName, cb: (node: TopLevelDeclaration) => T): T | undefined {
|
||||
switch (name.kind) {
|
||||
case SyntaxKind.Identifier:
|
||||
return cb(cast(name.parent, (x): x is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration(x) || isBindingElement(x)));
|
||||
case SyntaxKind.ArrayBindingPattern:
|
||||
case SyntaxKind.ObjectBindingPattern:
|
||||
return firstDefined(name.elements, em => isOmittedExpression(em) ? undefined : forEachTopLevelDeclarationInBindingName(em.name, cb));
|
||||
default:
|
||||
return Debug.assertNever(name);
|
||||
}
|
||||
}
|
||||
|
||||
function nameOfTopLevelDeclaration(d: TopLevelDeclaration): Identifier | undefined {
|
||||
return d.kind === SyntaxKind.ExpressionStatement ? d.expression.left.name : tryCast(d.name, isIdentifier);
|
||||
return isExpressionStatement(d) ? d.expression.left.name : tryCast(d.name, isIdentifier);
|
||||
}
|
||||
|
||||
function getTopLevelDeclarationStatement(d: TopLevelDeclaration): TopLevelDeclarationStatement {
|
||||
return isVariableDeclaration(d) ? d.parent.parent : d;
|
||||
switch (d.kind) {
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
return d.parent.parent;
|
||||
case SyntaxKind.BindingElement:
|
||||
return getTopLevelDeclarationStatement(
|
||||
cast(d.parent.parent, (p): p is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration(p) || isBindingElement(p)));
|
||||
default:
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
function addExportToChanges(sourceFile: SourceFile, decl: TopLevelDeclarationStatement, changes: textChanges.ChangeTracker, useEs6Exports: boolean): void {
|
||||
|
||||
+10
-10
@@ -1139,7 +1139,7 @@ namespace ts {
|
||||
const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host);
|
||||
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
|
||||
|
||||
const sourceMapper = getSourceMapper(getCanonicalFileName, currentDirectory, log, host, () => program);
|
||||
const sourceMapper = getSourceMapper(useCaseSensitiveFileNames, currentDirectory, log, host, () => program);
|
||||
|
||||
function getValidSourceFile(fileName: string): SourceFile {
|
||||
const sourceFile = program.getSourceFile(fileName);
|
||||
@@ -1232,11 +1232,11 @@ namespace ts {
|
||||
}
|
||||
|
||||
if (host.resolveModuleNames) {
|
||||
compilerHost.resolveModuleNames = (moduleNames, containingFile, reusedNames) => host.resolveModuleNames!(moduleNames, containingFile, reusedNames);
|
||||
compilerHost.resolveModuleNames = (moduleNames, containingFile, reusedNames, redirectedReference) => host.resolveModuleNames!(moduleNames, containingFile, reusedNames, redirectedReference);
|
||||
}
|
||||
if (host.resolveTypeReferenceDirectives) {
|
||||
compilerHost.resolveTypeReferenceDirectives = (typeReferenceDirectiveNames, containingFile) => {
|
||||
return host.resolveTypeReferenceDirectives!(typeReferenceDirectiveNames, containingFile);
|
||||
compilerHost.resolveTypeReferenceDirectives = (typeReferenceDirectiveNames, containingFile, redirectedReference) => {
|
||||
return host.resolveTypeReferenceDirectives!(typeReferenceDirectiveNames, containingFile, redirectedReference);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1276,7 +1276,7 @@ namespace ts {
|
||||
// not part of the new program.
|
||||
function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) {
|
||||
const oldSettingsKey = documentRegistry.getKeyForCompilationSettings(oldOptions);
|
||||
documentRegistry.releaseDocumentWithKey(oldSourceFile.path, oldSettingsKey);
|
||||
documentRegistry.releaseDocumentWithKey(oldSourceFile.resolvedPath, oldSettingsKey);
|
||||
}
|
||||
|
||||
function getOrCreateSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined {
|
||||
@@ -1496,7 +1496,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
/// Goto definition
|
||||
function getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] | undefined {
|
||||
function getDefinitionAtPosition(fileName: string, position: number): ReadonlyArray<DefinitionInfo> | undefined {
|
||||
synchronizeHostData();
|
||||
return GoToDefinition.getDefinitionAtPosition(program, getValidSourceFile(fileName), position);
|
||||
}
|
||||
@@ -1506,7 +1506,7 @@ namespace ts {
|
||||
return GoToDefinition.getDefinitionAndBoundSpan(program, getValidSourceFile(fileName), position);
|
||||
}
|
||||
|
||||
function getTypeDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] | undefined {
|
||||
function getTypeDefinitionAtPosition(fileName: string, position: number): ReadonlyArray<DefinitionInfo> | undefined {
|
||||
synchronizeHostData();
|
||||
return GoToDefinition.getTypeDefinitionAtPosition(program.getTypeChecker(), getValidSourceFile(fileName), position);
|
||||
}
|
||||
@@ -1519,7 +1519,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
/// References and Occurrences
|
||||
function getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined {
|
||||
function getOccurrencesAtPosition(fileName: string, position: number): ReadonlyArray<ReferenceEntry> | undefined {
|
||||
return flatMap(getDocumentHighlights(fileName, position, [fileName]), entry => entry.highlightSpans.map<ReferenceEntry>(highlightSpan => ({
|
||||
fileName: entry.fileName,
|
||||
textSpan: highlightSpan.textSpan,
|
||||
@@ -1533,7 +1533,7 @@ namespace ts {
|
||||
const normalizedFileName = normalizePath(fileName);
|
||||
Debug.assert(filesToSearch.some(f => normalizePath(f) === normalizedFileName));
|
||||
synchronizeHostData();
|
||||
const sourceFilesToSearch = map(filesToSearch, f => Debug.assertDefined(program.getSourceFile(f)));
|
||||
const sourceFilesToSearch = filesToSearch.map(getValidSourceFile);
|
||||
const sourceFile = getValidSourceFile(fileName);
|
||||
return DocumentHighlights.getDocumentHighlights(program, cancellationToken, sourceFile, position, sourceFilesToSearch);
|
||||
}
|
||||
@@ -1542,7 +1542,7 @@ namespace ts {
|
||||
synchronizeHostData();
|
||||
const sourceFile = getValidSourceFile(fileName);
|
||||
const node = getTouchingPropertyName(sourceFile, position);
|
||||
if (isIdentifier(node) && isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) {
|
||||
if (isIdentifier(node) && (isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) && isIntrinsicJsxName(node.escapedText)) {
|
||||
const { openingElement, closingElement } = node.parent.parent;
|
||||
return [openingElement, closingElement].map((node): RenameLocation =>
|
||||
({ fileName: sourceFile.fileName, textSpan: createTextSpanFromNode(node.tagName, sourceFile) }));
|
||||
|
||||
@@ -331,19 +331,19 @@ namespace ts {
|
||||
private loggingEnabled = false;
|
||||
private tracingEnabled = false;
|
||||
|
||||
public resolveModuleNames: (moduleName: string[], containingFile: string) => ResolvedModuleFull[];
|
||||
public resolveTypeReferenceDirectives: (typeDirectiveNames: string[], containingFile: string) => ResolvedTypeReferenceDirective[];
|
||||
public resolveModuleNames: (moduleName: string[], containingFile: string) => (ResolvedModuleFull | undefined)[];
|
||||
public resolveTypeReferenceDirectives: (typeDirectiveNames: string[], containingFile: string) => (ResolvedTypeReferenceDirective | undefined)[];
|
||||
public directoryExists: (directoryName: string) => boolean;
|
||||
|
||||
constructor(private shimHost: LanguageServiceShimHost) {
|
||||
// if shimHost is a COM object then property check will become method call with no arguments.
|
||||
// 'in' does not have this effect.
|
||||
if ("getModuleResolutionsForFile" in this.shimHost) {
|
||||
this.resolveModuleNames = (moduleNames: string[], containingFile: string): ResolvedModuleFull[] => {
|
||||
this.resolveModuleNames = (moduleNames, containingFile) => {
|
||||
const resolutionsInFile = <MapLike<string>>JSON.parse(this.shimHost.getModuleResolutionsForFile!(containingFile)); // TODO: GH#18217
|
||||
return map(moduleNames, name => {
|
||||
const result = getProperty(resolutionsInFile, name);
|
||||
return result ? { resolvedFileName: result, extension: extensionFromPath(result), isExternalLibraryImport: false } : undefined!; // TODO: GH#18217
|
||||
return result ? { resolvedFileName: result, extension: extensionFromPath(result), isExternalLibraryImport: false } : undefined;
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -351,9 +351,9 @@ namespace ts {
|
||||
this.directoryExists = directoryName => this.shimHost.directoryExists(directoryName);
|
||||
}
|
||||
if ("getTypeReferenceDirectiveResolutionsForFile" in this.shimHost) {
|
||||
this.resolveTypeReferenceDirectives = (typeDirectiveNames: string[], containingFile: string) => {
|
||||
this.resolveTypeReferenceDirectives = (typeDirectiveNames, containingFile) => {
|
||||
const typeDirectivesForFile = <MapLike<ResolvedTypeReferenceDirective>>JSON.parse(this.shimHost.getTypeReferenceDirectiveResolutionsForFile!(containingFile)); // TODO: GH#18217
|
||||
return map(typeDirectiveNames, name => getProperty(typeDirectivesForFile, name)!); // TODO: GH#18217
|
||||
return map(typeDirectiveNames, name => getProperty(typeDirectivesForFile, name));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,17 +10,22 @@ namespace ts {
|
||||
}
|
||||
|
||||
export function getSourceMapper(
|
||||
getCanonicalFileName: GetCanonicalFileName,
|
||||
useCaseSensitiveFileNames: boolean,
|
||||
currentDirectory: string,
|
||||
log: (message: string) => void,
|
||||
host: LanguageServiceHost,
|
||||
getProgram: () => Program,
|
||||
): SourceMapper {
|
||||
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
|
||||
let sourcemappedFileCache: SourceFileLikeCache;
|
||||
return { tryGetSourcePosition, tryGetGeneratedPosition, toLineColumnOffset, clearCache };
|
||||
|
||||
function toPath(fileName: string) {
|
||||
return ts.toPath(fileName, currentDirectory, getCanonicalFileName);
|
||||
}
|
||||
|
||||
function scanForSourcemapURL(fileName: string) {
|
||||
const mappedFile = sourcemappedFileCache.get(toPath(fileName, currentDirectory, getCanonicalFileName));
|
||||
const mappedFile = sourcemappedFileCache.get(toPath(fileName));
|
||||
if (!mappedFile) {
|
||||
return;
|
||||
}
|
||||
@@ -38,7 +43,7 @@ namespace ts {
|
||||
return file.sourceMapper = createDocumentPositionMapper({
|
||||
getSourceFileLike: s => {
|
||||
// Lookup file in program, if provided
|
||||
const file = program && program.getSourceFile(s);
|
||||
const file = program && program.getSourceFileByPath(s);
|
||||
// file returned here could be .d.ts when asked for .ts file if projectReferences and module resolution created this source file
|
||||
if (file === undefined || file.resolvedPath !== s) {
|
||||
// Otherwise check the cache (which may hit disk)
|
||||
@@ -76,7 +81,7 @@ namespace ts {
|
||||
}
|
||||
possibleMapLocations.push(fileName + ".map");
|
||||
for (const location of possibleMapLocations) {
|
||||
const mapPath = toPath(location, getDirectoryPath(fileName), getCanonicalFileName);
|
||||
const mapPath = ts.toPath(location, getDirectoryPath(fileName), getCanonicalFileName);
|
||||
if (host.fileExists(mapPath)) {
|
||||
return convertDocumentToSourceMapper(file, host.readFile(mapPath)!, mapPath); // TODO: GH#18217
|
||||
}
|
||||
@@ -95,7 +100,11 @@ namespace ts {
|
||||
|
||||
function tryGetGeneratedPosition(info: DocumentPosition): DocumentPosition | undefined {
|
||||
const program = getProgram();
|
||||
const declarationPath = getDeclarationEmitOutputFilePathWorker(info.fileName, program.getCompilerOptions(), currentDirectory, program.getCommonSourceDirectory(), getCanonicalFileName);
|
||||
const options = program.getCompilerOptions();
|
||||
const outPath = options.outFile || options.out;
|
||||
const declarationPath = outPath ?
|
||||
removeFileExtension(outPath) + Extension.Dts :
|
||||
getDeclarationEmitOutputFilePathWorker(info.fileName, program.getCompilerOptions(), currentDirectory, program.getCommonSourceDirectory(), getCanonicalFileName);
|
||||
if (declarationPath === undefined) return undefined;
|
||||
const declarationFile = getFile(declarationPath);
|
||||
if (!declarationFile) return undefined;
|
||||
@@ -104,12 +113,16 @@ namespace ts {
|
||||
}
|
||||
|
||||
function getFile(fileName: string): SourceFileLike | undefined {
|
||||
return getProgram().getSourceFile(fileName) || sourcemappedFileCache.get(toPath(fileName, currentDirectory, getCanonicalFileName));
|
||||
const path = toPath(fileName);
|
||||
const file = getProgram().getSourceFileByPath(path);
|
||||
if (file && file.resolvedPath === path) {
|
||||
return file;
|
||||
}
|
||||
return sourcemappedFileCache.get(path);
|
||||
}
|
||||
|
||||
function toLineColumnOffset(fileName: string, position: number): LineAndCharacter {
|
||||
const path = toPath(fileName, currentDirectory, getCanonicalFileName);
|
||||
const file = getProgram().getSourceFile(path) || sourcemappedFileCache.get(path)!; // TODO: GH#18217
|
||||
const file = getFile(fileName)!; // TODO: GH#18217
|
||||
return file.getLineAndCharacterOfPosition(position);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,733 @@
|
||||
/* @internal */
|
||||
namespace ts.Completions.StringCompletions {
|
||||
export function getStringLiteralCompletions(sourceFile: SourceFile, position: number, contextToken: Node | undefined, checker: TypeChecker, options: CompilerOptions, host: LanguageServiceHost, log: Log, preferences: UserPreferences): CompletionInfo | undefined {
|
||||
if (isInReferenceComment(sourceFile, position)) {
|
||||
const entries = getTripleSlashReferenceCompletion(sourceFile, position, options, host);
|
||||
return entries && convertPathCompletions(entries);
|
||||
}
|
||||
if (isInString(sourceFile, position, contextToken)) {
|
||||
return !contextToken || !isStringLiteralLike(contextToken)
|
||||
? undefined
|
||||
: convertStringLiteralCompletions(getStringLiteralCompletionEntries(sourceFile, contextToken, position, checker, options, host), sourceFile, checker, log, preferences);
|
||||
}
|
||||
}
|
||||
|
||||
function convertStringLiteralCompletions(completion: StringLiteralCompletion | undefined, sourceFile: SourceFile, checker: TypeChecker, log: Log, preferences: UserPreferences): CompletionInfo | undefined {
|
||||
if (completion === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
switch (completion.kind) {
|
||||
case StringLiteralCompletionKind.Paths:
|
||||
return convertPathCompletions(completion.paths);
|
||||
case StringLiteralCompletionKind.Properties: {
|
||||
const entries: CompletionEntry[] = [];
|
||||
getCompletionEntriesFromSymbols(completion.symbols, entries, sourceFile, sourceFile, checker, ScriptTarget.ESNext, log, CompletionKind.String, preferences); // Target will not be used, so arbitrary
|
||||
return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: completion.hasIndexSignature, entries };
|
||||
}
|
||||
case StringLiteralCompletionKind.Types: {
|
||||
const entries = completion.types.map(type => ({ name: type.value, kindModifiers: ScriptElementKindModifier.none, kind: ScriptElementKind.string, sortText: "0" }));
|
||||
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: completion.isNewIdentifier, entries };
|
||||
}
|
||||
default:
|
||||
return Debug.assertNever(completion);
|
||||
}
|
||||
}
|
||||
|
||||
export function getStringLiteralCompletionDetails(name: string, sourceFile: SourceFile, position: number, contextToken: Node | undefined, checker: TypeChecker, options: CompilerOptions, host: LanguageServiceHost, cancellationToken: CancellationToken) {
|
||||
if (!contextToken || !isStringLiteralLike(contextToken)) return undefined;
|
||||
const completions = getStringLiteralCompletionEntries(sourceFile, contextToken, position, checker, options, host);
|
||||
return completions && stringLiteralCompletionDetails(name, contextToken, completions, sourceFile, checker, cancellationToken);
|
||||
}
|
||||
|
||||
function stringLiteralCompletionDetails(name: string, location: Node, completion: StringLiteralCompletion, sourceFile: SourceFile, checker: TypeChecker, cancellationToken: CancellationToken): CompletionEntryDetails | undefined {
|
||||
switch (completion.kind) {
|
||||
case StringLiteralCompletionKind.Paths: {
|
||||
const match = find(completion.paths, p => p.name === name);
|
||||
return match && createCompletionDetails(name, kindModifiersFromExtension(match.extension), match.kind, [textPart(name)]);
|
||||
}
|
||||
case StringLiteralCompletionKind.Properties: {
|
||||
const match = find(completion.symbols, s => s.name === name);
|
||||
return match && createCompletionDetailsForSymbol(match, checker, sourceFile, location, cancellationToken);
|
||||
}
|
||||
case StringLiteralCompletionKind.Types:
|
||||
return find(completion.types, t => t.value === name) ? createCompletionDetails(name, ScriptElementKindModifier.none, ScriptElementKind.typeElement, [textPart(name)]) : undefined;
|
||||
default:
|
||||
return Debug.assertNever(completion);
|
||||
}
|
||||
}
|
||||
|
||||
function convertPathCompletions(pathCompletions: ReadonlyArray<PathCompletion>): CompletionInfo {
|
||||
const isGlobalCompletion = false; // We don't want the editor to offer any other completions, such as snippets, inside a comment.
|
||||
const isNewIdentifierLocation = true; // The user may type in a path that doesn't yet exist, creating a "new identifier" with respect to the collection of identifiers the server is aware of.
|
||||
const entries = pathCompletions.map(({ name, kind, span, extension }): CompletionEntry =>
|
||||
({ name, kind, kindModifiers: kindModifiersFromExtension(extension), sortText: "0", replacementSpan: span }));
|
||||
return { isGlobalCompletion, isMemberCompletion: false, isNewIdentifierLocation, entries };
|
||||
}
|
||||
function kindModifiersFromExtension(extension: Extension | undefined): ScriptElementKindModifier {
|
||||
switch (extension) {
|
||||
case Extension.Dts: return ScriptElementKindModifier.dtsModifier;
|
||||
case Extension.Js: return ScriptElementKindModifier.jsModifier;
|
||||
case Extension.Json: return ScriptElementKindModifier.jsonModifier;
|
||||
case Extension.Jsx: return ScriptElementKindModifier.jsxModifier;
|
||||
case Extension.Ts: return ScriptElementKindModifier.tsModifier;
|
||||
case Extension.Tsx: return ScriptElementKindModifier.tsxModifier;
|
||||
case undefined: return ScriptElementKindModifier.none;
|
||||
default:
|
||||
return Debug.assertNever(extension);
|
||||
}
|
||||
}
|
||||
|
||||
const enum StringLiteralCompletionKind { Paths, Properties, Types }
|
||||
interface StringLiteralCompletionsFromProperties {
|
||||
readonly kind: StringLiteralCompletionKind.Properties;
|
||||
readonly symbols: ReadonlyArray<Symbol>;
|
||||
readonly hasIndexSignature: boolean;
|
||||
}
|
||||
interface StringLiteralCompletionsFromTypes {
|
||||
readonly kind: StringLiteralCompletionKind.Types;
|
||||
readonly types: ReadonlyArray<StringLiteralType>;
|
||||
readonly isNewIdentifier: boolean;
|
||||
}
|
||||
type StringLiteralCompletion = { readonly kind: StringLiteralCompletionKind.Paths, readonly paths: ReadonlyArray<PathCompletion> } | StringLiteralCompletionsFromProperties | StringLiteralCompletionsFromTypes;
|
||||
function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringLiteralLike, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost): StringLiteralCompletion | undefined {
|
||||
const { parent } = node;
|
||||
switch (parent.kind) {
|
||||
case SyntaxKind.LiteralType:
|
||||
switch (parent.parent.kind) {
|
||||
case SyntaxKind.TypeReference:
|
||||
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(parent as LiteralTypeNode)), isNewIdentifier: false };
|
||||
case SyntaxKind.IndexedAccessType:
|
||||
// Get all apparent property names
|
||||
// i.e. interface Foo {
|
||||
// foo: string;
|
||||
// bar: string;
|
||||
// }
|
||||
// let x: Foo["/*completion position*/"]
|
||||
return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode((parent.parent as IndexedAccessTypeNode).objectType));
|
||||
case SyntaxKind.ImportType:
|
||||
return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker) };
|
||||
case SyntaxKind.UnionType: {
|
||||
if (!isTypeReferenceNode(parent.parent.parent)) return undefined;
|
||||
const alreadyUsedTypes = getAlreadyUsedTypesInStringLiteralUnion(parent.parent as UnionTypeNode, parent as LiteralTypeNode);
|
||||
const types = getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(parent.parent as UnionTypeNode)).filter(t => !contains(alreadyUsedTypes, t.value));
|
||||
return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier: false };
|
||||
}
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
||||
case SyntaxKind.PropertyAssignment:
|
||||
if (isObjectLiteralExpression(parent.parent) && (<PropertyAssignment>parent).name === node) {
|
||||
// Get quoted name of properties of the object literal expression
|
||||
// i.e. interface ConfigFiles {
|
||||
// 'jspm:dev': string
|
||||
// }
|
||||
// let files: ConfigFiles = {
|
||||
// '/*completion position*/'
|
||||
// }
|
||||
//
|
||||
// function foo(c: ConfigFiles) {}
|
||||
// foo({
|
||||
// '/*completion position*/'
|
||||
// });
|
||||
return stringLiteralCompletionsFromProperties(typeChecker.getContextualType(parent.parent));
|
||||
}
|
||||
return fromContextualType();
|
||||
|
||||
case SyntaxKind.ElementAccessExpression: {
|
||||
const { expression, argumentExpression } = parent as ElementAccessExpression;
|
||||
if (node === argumentExpression) {
|
||||
// Get all names of properties on the expression
|
||||
// i.e. interface A {
|
||||
// 'prop1': string
|
||||
// }
|
||||
// let a: A;
|
||||
// a['/*completion position*/']
|
||||
return stringLiteralCompletionsFromProperties(typeChecker.getTypeAtLocation(expression));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
case SyntaxKind.CallExpression:
|
||||
case SyntaxKind.NewExpression:
|
||||
if (!isRequireCall(parent, /*checkArgumentIsStringLiteralLike*/ false) && !isImportCall(parent)) {
|
||||
const argumentInfo = SignatureHelp.getArgumentInfoForCompletions(node, position, sourceFile);
|
||||
// Get string literal completions from specialized signatures of the target
|
||||
// i.e. declare function f(a: 'A');
|
||||
// f("/*completion position*/")
|
||||
return argumentInfo ? getStringLiteralCompletionsFromSignature(argumentInfo, typeChecker) : fromContextualType();
|
||||
}
|
||||
// falls through (is `require("")` or `import("")`)
|
||||
|
||||
case SyntaxKind.ImportDeclaration:
|
||||
case SyntaxKind.ExportDeclaration:
|
||||
case SyntaxKind.ExternalModuleReference:
|
||||
// Get all known external module names or complete a path to a module
|
||||
// i.e. import * as ns from "/*completion position*/";
|
||||
// var y = import("/*completion position*/");
|
||||
// import x = require("/*completion position*/");
|
||||
// var y = require("/*completion position*/");
|
||||
// export * from "/*completion position*/";
|
||||
return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker) };
|
||||
|
||||
default:
|
||||
return fromContextualType();
|
||||
}
|
||||
|
||||
function fromContextualType(): StringLiteralCompletion {
|
||||
// Get completion for string literal from string literal type
|
||||
// i.e. var x: "hi" | "hello" = "/*completion position*/"
|
||||
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker)), isNewIdentifier: false };
|
||||
}
|
||||
}
|
||||
|
||||
function getAlreadyUsedTypesInStringLiteralUnion(union: UnionTypeNode, current: LiteralTypeNode): ReadonlyArray<string> {
|
||||
return mapDefined(union.types, type =>
|
||||
type !== current && isLiteralTypeNode(type) && isStringLiteral(type.literal) ? type.literal.text : undefined);
|
||||
}
|
||||
|
||||
function getStringLiteralCompletionsFromSignature(argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker): StringLiteralCompletionsFromTypes {
|
||||
let isNewIdentifier = false;
|
||||
|
||||
const uniques = createMap<true>();
|
||||
const candidates: Signature[] = [];
|
||||
checker.getResolvedSignature(argumentInfo.invocation, candidates, argumentInfo.argumentCount);
|
||||
const types = flatMap(candidates, candidate => {
|
||||
if (!candidate.hasRestParameter && argumentInfo.argumentCount > candidate.parameters.length) return;
|
||||
const type = checker.getParameterType(candidate, argumentInfo.argumentIndex);
|
||||
isNewIdentifier = isNewIdentifier || !!(type.flags & TypeFlags.String);
|
||||
return getStringLiteralTypes(type, uniques);
|
||||
});
|
||||
|
||||
return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier };
|
||||
}
|
||||
|
||||
function stringLiteralCompletionsFromProperties(type: Type | undefined): StringLiteralCompletionsFromProperties | undefined {
|
||||
return type && { kind: StringLiteralCompletionKind.Properties, symbols: type.getApparentProperties(), hasIndexSignature: hasIndexSignature(type) };
|
||||
}
|
||||
|
||||
function getStringLiteralTypes(type: Type | undefined, uniques = createMap<true>()): ReadonlyArray<StringLiteralType> {
|
||||
if (!type) return emptyArray;
|
||||
type = skipConstraint(type);
|
||||
return type.isUnion()
|
||||
? flatMap(type.types, t => getStringLiteralTypes(t, uniques))
|
||||
: type.isStringLiteral() && !(type.flags & TypeFlags.EnumLiteral) && addToSeen(uniques, type.value)
|
||||
? [type]
|
||||
: emptyArray;
|
||||
}
|
||||
|
||||
interface NameAndKind {
|
||||
readonly name: string;
|
||||
readonly kind: ScriptElementKind.scriptElement | ScriptElementKind.directory | ScriptElementKind.externalModuleName;
|
||||
readonly extension: Extension | undefined;
|
||||
}
|
||||
interface PathCompletion extends NameAndKind {
|
||||
readonly span: TextSpan | undefined;
|
||||
}
|
||||
|
||||
function nameAndKind(name: string, kind: NameAndKind["kind"], extension: Extension | undefined): NameAndKind {
|
||||
return { name, kind, extension };
|
||||
}
|
||||
function directoryResult(name: string): NameAndKind {
|
||||
return nameAndKind(name, ScriptElementKind.directory, /*extension*/ undefined);
|
||||
}
|
||||
|
||||
function addReplacementSpans(text: string, textStart: number, names: ReadonlyArray<NameAndKind>): ReadonlyArray<PathCompletion> {
|
||||
const span = getDirectoryFragmentTextSpan(text, textStart);
|
||||
return names.map(({ name, kind, extension }): PathCompletion => ({ name, kind, extension, span }));
|
||||
}
|
||||
|
||||
function getStringLiteralCompletionsFromModuleNames(sourceFile: SourceFile, node: LiteralExpression, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): ReadonlyArray<PathCompletion> {
|
||||
return addReplacementSpans(node.text, node.getStart(sourceFile) + 1, getStringLiteralCompletionsFromModuleNamesWorker(sourceFile, node, compilerOptions, host, typeChecker));
|
||||
}
|
||||
|
||||
function getStringLiteralCompletionsFromModuleNamesWorker(sourceFile: SourceFile, node: LiteralExpression, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): ReadonlyArray<NameAndKind> {
|
||||
const literalValue = normalizeSlashes(node.text);
|
||||
|
||||
const scriptPath = sourceFile.path;
|
||||
const scriptDirectory = getDirectoryPath(scriptPath);
|
||||
|
||||
return isPathRelativeToScript(literalValue) || !compilerOptions.baseUrl && (isRootedDiskPath(literalValue) || isUrl(literalValue))
|
||||
? getCompletionEntriesForRelativeModules(literalValue, scriptDirectory, compilerOptions, host, scriptPath)
|
||||
: getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, compilerOptions, host, typeChecker);
|
||||
}
|
||||
|
||||
interface ExtensionOptions {
|
||||
readonly extensions: ReadonlyArray<Extension>;
|
||||
readonly includeExtensions: boolean;
|
||||
}
|
||||
function getExtensionOptions(compilerOptions: CompilerOptions, includeExtensions = false): ExtensionOptions {
|
||||
return { extensions: getSupportedExtensionsForModuleResolution(compilerOptions), includeExtensions };
|
||||
}
|
||||
function getCompletionEntriesForRelativeModules(literalValue: string, scriptDirectory: string, compilerOptions: CompilerOptions, host: LanguageServiceHost, scriptPath: Path) {
|
||||
const extensionOptions = getExtensionOptions(compilerOptions);
|
||||
if (compilerOptions.rootDirs) {
|
||||
return getCompletionEntriesForDirectoryFragmentWithRootDirs(
|
||||
compilerOptions.rootDirs, literalValue, scriptDirectory, extensionOptions, compilerOptions, host, scriptPath);
|
||||
}
|
||||
else {
|
||||
return getCompletionEntriesForDirectoryFragment(literalValue, scriptDirectory, extensionOptions, host, scriptPath);
|
||||
}
|
||||
}
|
||||
|
||||
function getSupportedExtensionsForModuleResolution(compilerOptions: CompilerOptions): ReadonlyArray<Extension> {
|
||||
const extensions = getSupportedExtensions(compilerOptions);
|
||||
return compilerOptions.resolveJsonModule && getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs ?
|
||||
extensions.concat(Extension.Json) :
|
||||
extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a script path and returns paths for all potential folders that could be merged with its
|
||||
* containing folder via the "rootDirs" compiler option
|
||||
*/
|
||||
function getBaseDirectoriesFromRootDirs(rootDirs: string[], basePath: string, scriptPath: string, ignoreCase: boolean): string[] {
|
||||
// Make all paths absolute/normalized if they are not already
|
||||
rootDirs = rootDirs.map(rootDirectory => normalizePath(isRootedDiskPath(rootDirectory) ? rootDirectory : combinePaths(basePath, rootDirectory)));
|
||||
|
||||
// Determine the path to the directory containing the script relative to the root directory it is contained within
|
||||
const relativeDirectory = firstDefined(rootDirs, rootDirectory =>
|
||||
containsPath(rootDirectory, scriptPath, basePath, ignoreCase) ? scriptPath.substr(rootDirectory.length) : undefined)!; // TODO: GH#18217
|
||||
|
||||
// Now find a path for each potential directory that is to be merged with the one containing the script
|
||||
return deduplicate<string>(
|
||||
rootDirs.map(rootDirectory => combinePaths(rootDirectory, relativeDirectory)),
|
||||
equateStringsCaseSensitive,
|
||||
compareStringsCaseSensitive);
|
||||
}
|
||||
|
||||
function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensionOptions: ExtensionOptions, compilerOptions: CompilerOptions, host: LanguageServiceHost, exclude?: string): NameAndKind[] {
|
||||
const basePath = compilerOptions.project || host.getCurrentDirectory();
|
||||
const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames());
|
||||
const baseDirectories = getBaseDirectoriesFromRootDirs(rootDirs, basePath, scriptPath, ignoreCase);
|
||||
|
||||
const result: NameAndKind[] = [];
|
||||
|
||||
for (const baseDirectory of baseDirectories) {
|
||||
getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensionOptions, host, exclude, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a path ending at a directory, gets the completions for the path, and filters for those entries containing the basename.
|
||||
*/
|
||||
function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, { extensions, includeExtensions }: ExtensionOptions, host: LanguageServiceHost, exclude?: string, result: NameAndKind[] = []): NameAndKind[] {
|
||||
if (fragment === undefined) {
|
||||
fragment = "";
|
||||
}
|
||||
|
||||
fragment = normalizeSlashes(fragment);
|
||||
|
||||
/**
|
||||
* Remove the basename from the path. Note that we don't use the basename to filter completions;
|
||||
* the client is responsible for refining completions.
|
||||
*/
|
||||
if (!hasTrailingDirectorySeparator(fragment)) {
|
||||
fragment = getDirectoryPath(fragment);
|
||||
}
|
||||
|
||||
if (fragment === "") {
|
||||
fragment = "." + directorySeparator;
|
||||
}
|
||||
|
||||
fragment = ensureTrailingDirectorySeparator(fragment);
|
||||
|
||||
// const absolutePath = normalizeAndPreserveTrailingSlash(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment)); // TODO(rbuckton): should use resolvePaths
|
||||
const absolutePath = resolvePath(scriptPath, fragment);
|
||||
const baseDirectory = hasTrailingDirectorySeparator(absolutePath) ? absolutePath : getDirectoryPath(absolutePath);
|
||||
|
||||
const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames());
|
||||
if (!tryDirectoryExists(host, baseDirectory)) return result;
|
||||
|
||||
// Enumerate the available files if possible
|
||||
const files = tryReadDirectory(host, baseDirectory, extensions, /*exclude*/ undefined, /*include*/ ["./*"]);
|
||||
|
||||
if (files) {
|
||||
/**
|
||||
* Multiple file entries might map to the same truncated name once we remove extensions
|
||||
* (happens iff includeExtensions === false)so we use a set-like data structure. Eg:
|
||||
*
|
||||
* both foo.ts and foo.tsx become foo
|
||||
*/
|
||||
const foundFiles = createMap<Extension | undefined>(); // maps file to its extension
|
||||
for (let filePath of files) {
|
||||
filePath = normalizePath(filePath);
|
||||
if (exclude && comparePaths(filePath, exclude, scriptPath, ignoreCase) === Comparison.EqualTo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const foundFileName = includeExtensions || fileExtensionIs(filePath, Extension.Json) ? getBaseFileName(filePath) : removeFileExtension(getBaseFileName(filePath));
|
||||
foundFiles.set(foundFileName, tryGetExtensionFromPath(filePath));
|
||||
}
|
||||
|
||||
foundFiles.forEach((ext, foundFile) => {
|
||||
result.push(nameAndKind(foundFile, ScriptElementKind.scriptElement, ext));
|
||||
});
|
||||
}
|
||||
|
||||
// If possible, get folder completion as well
|
||||
const directories = tryGetDirectories(host, baseDirectory);
|
||||
|
||||
if (directories) {
|
||||
for (const directory of directories) {
|
||||
const directoryName = getBaseFileName(normalizePath(directory));
|
||||
if (directoryName !== "@types") {
|
||||
result.push(directoryResult(directoryName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for a version redirect
|
||||
const packageJsonPath = findPackageJson(baseDirectory, host);
|
||||
if (packageJsonPath) {
|
||||
const packageJson = readJson(packageJsonPath, host as { readFile: (filename: string) => string | undefined });
|
||||
const typesVersions = (packageJson as any).typesVersions;
|
||||
if (typeof typesVersions === "object") {
|
||||
const versionResult = getPackageJsonTypesVersionsPaths(typesVersions);
|
||||
const versionPaths = versionResult && versionResult.paths;
|
||||
const rest = absolutePath.slice(ensureTrailingDirectorySeparator(baseDirectory).length);
|
||||
if (versionPaths) {
|
||||
addCompletionEntriesFromPaths(result, rest, baseDirectory, extensions, versionPaths, host);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function addCompletionEntriesFromPaths(result: NameAndKind[], fragment: string, baseDirectory: string, fileExtensions: ReadonlyArray<string>, paths: MapLike<string[]>, host: LanguageServiceHost) {
|
||||
for (const path in paths) {
|
||||
if (!hasProperty(paths, path)) continue;
|
||||
const patterns = paths[path];
|
||||
if (patterns) {
|
||||
for (const { name, kind, extension } of getCompletionsForPathMapping(path, patterns, fragment, baseDirectory, fileExtensions, host)) {
|
||||
// Path mappings may provide a duplicate way to get to something we've already added, so don't add again.
|
||||
if (!result.some(entry => entry.name === name)) {
|
||||
result.push(nameAndKind(name, kind, extension));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check all of the declared modules and those in node modules. Possible sources of modules:
|
||||
* Modules that are found by the type checker
|
||||
* Modules found relative to "baseUrl" compliler options (including patterns from "paths" compiler option)
|
||||
* Modules from node_modules (i.e. those listed in package.json)
|
||||
* This includes all files that are found in node_modules/moduleName/ with acceptable file extensions
|
||||
*/
|
||||
function getCompletionEntriesForNonRelativeModules(fragment: string, scriptPath: string, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): ReadonlyArray<NameAndKind> {
|
||||
const { baseUrl, paths } = compilerOptions;
|
||||
|
||||
const result: NameAndKind[] = [];
|
||||
|
||||
const extensionOptions = getExtensionOptions(compilerOptions);
|
||||
if (baseUrl) {
|
||||
const projectDir = compilerOptions.project || host.getCurrentDirectory();
|
||||
const absolute = normalizePath(combinePaths(projectDir, baseUrl));
|
||||
getCompletionEntriesForDirectoryFragment(fragment, absolute, extensionOptions, host, /*exclude*/ undefined, result);
|
||||
if (paths) {
|
||||
addCompletionEntriesFromPaths(result, fragment, absolute, extensionOptions.extensions, paths, host);
|
||||
}
|
||||
}
|
||||
|
||||
const fragmentDirectory = getFragmentDirectory(fragment);
|
||||
for (const ambientName of getAmbientModuleCompletions(fragment, fragmentDirectory, typeChecker)) {
|
||||
result.push(nameAndKind(ambientName, ScriptElementKind.externalModuleName, /*extension*/ undefined));
|
||||
}
|
||||
|
||||
getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, fragmentDirectory, extensionOptions, result);
|
||||
|
||||
if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs) {
|
||||
// If looking for a global package name, don't just include everything in `node_modules` because that includes dependencies' own dependencies.
|
||||
// (But do if we didn't find anything, e.g. 'package.json' missing.)
|
||||
let foundGlobal = false;
|
||||
if (fragmentDirectory === undefined) {
|
||||
for (const moduleName of enumerateNodeModulesVisibleToScript(host, scriptPath)) {
|
||||
if (!result.some(entry => entry.name === moduleName)) {
|
||||
foundGlobal = true;
|
||||
result.push(nameAndKind(moduleName, ScriptElementKind.externalModuleName, /*extension*/ undefined));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!foundGlobal) {
|
||||
forEachAncestorDirectory(scriptPath, ancestor => {
|
||||
const nodeModules = combinePaths(ancestor, "node_modules");
|
||||
if (tryDirectoryExists(host, nodeModules)) {
|
||||
getCompletionEntriesForDirectoryFragment(fragment, nodeModules, extensionOptions, host, /*exclude*/ undefined, result);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function getFragmentDirectory(fragment: string): string | undefined {
|
||||
return containsSlash(fragment) ? hasTrailingDirectorySeparator(fragment) ? fragment : getDirectoryPath(fragment) : undefined;
|
||||
}
|
||||
|
||||
function getCompletionsForPathMapping(
|
||||
path: string, patterns: ReadonlyArray<string>, fragment: string, baseUrl: string, fileExtensions: ReadonlyArray<string>, host: LanguageServiceHost,
|
||||
): ReadonlyArray<NameAndKind> {
|
||||
if (!endsWith(path, "*")) {
|
||||
// For a path mapping "foo": ["/x/y/z.ts"], add "foo" itself as a completion.
|
||||
return !stringContains(path, "*") ? justPathMappingName(path) : emptyArray;
|
||||
}
|
||||
|
||||
const pathPrefix = path.slice(0, path.length - 1);
|
||||
const remainingFragment = tryRemovePrefix(fragment, pathPrefix);
|
||||
return remainingFragment === undefined ? justPathMappingName(pathPrefix) : flatMap(patterns, pattern =>
|
||||
getModulesForPathsPattern(remainingFragment, baseUrl, pattern, fileExtensions, host));
|
||||
|
||||
function justPathMappingName(name: string): ReadonlyArray<NameAndKind> {
|
||||
return startsWith(name, fragment) ? [directoryResult(name)] : emptyArray;
|
||||
}
|
||||
}
|
||||
|
||||
function getModulesForPathsPattern(fragment: string, baseUrl: string, pattern: string, fileExtensions: ReadonlyArray<string>, host: LanguageServiceHost): ReadonlyArray<NameAndKind> | undefined {
|
||||
if (!host.readDirectory) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const parsed = hasZeroOrOneAsteriskCharacter(pattern) ? tryParsePattern(pattern) : undefined;
|
||||
if (!parsed) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// The prefix has two effective parts: the directory path and the base component after the filepath that is not a
|
||||
// full directory component. For example: directory/path/of/prefix/base*
|
||||
const normalizedPrefix = resolvePath(parsed.prefix);
|
||||
const normalizedPrefixDirectory = hasTrailingDirectorySeparator(parsed.prefix) ? normalizedPrefix : getDirectoryPath(normalizedPrefix);
|
||||
const normalizedPrefixBase = hasTrailingDirectorySeparator(parsed.prefix) ? "" : getBaseFileName(normalizedPrefix);
|
||||
|
||||
const fragmentHasPath = containsSlash(fragment);
|
||||
const fragmentDirectory = fragmentHasPath ? hasTrailingDirectorySeparator(fragment) ? fragment : getDirectoryPath(fragment) : undefined;
|
||||
|
||||
// Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call
|
||||
const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + fragmentDirectory) : normalizedPrefixDirectory;
|
||||
|
||||
const normalizedSuffix = normalizePath(parsed.suffix);
|
||||
// Need to normalize after combining: If we combinePaths("a", "../b"), we want "b" and not "a/../b".
|
||||
const baseDirectory = normalizePath(combinePaths(baseUrl, expandedPrefixDirectory));
|
||||
const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator(baseDirectory) + normalizedPrefixBase;
|
||||
|
||||
// If we have a suffix, then we need to read the directory all the way down. We could create a glob
|
||||
// that encodes the suffix, but we would have to escape the character "?" which readDirectory
|
||||
// doesn't support. For now, this is safer but slower
|
||||
const includeGlob = normalizedSuffix ? "**/*" : "./*";
|
||||
|
||||
const matches = mapDefined(tryReadDirectory(host, baseDirectory, fileExtensions, /*exclude*/ undefined, [includeGlob]), match => {
|
||||
const extension = tryGetExtensionFromPath(match);
|
||||
const name = trimPrefixAndSuffix(match);
|
||||
return name === undefined ? undefined : nameAndKind(removeFileExtension(name), ScriptElementKind.scriptElement, extension);
|
||||
});
|
||||
const directories = mapDefined(tryGetDirectories(host, baseDirectory).map(d => combinePaths(baseDirectory, d)), dir => {
|
||||
const name = trimPrefixAndSuffix(dir);
|
||||
return name === undefined ? undefined : directoryResult(name);
|
||||
});
|
||||
return [...matches, ...directories];
|
||||
|
||||
function trimPrefixAndSuffix(path: string): string | undefined {
|
||||
const inner = withoutStartAndEnd(normalizePath(path), completePrefix, normalizedSuffix);
|
||||
return inner === undefined ? undefined : removeLeadingDirectorySeparator(inner);
|
||||
}
|
||||
}
|
||||
|
||||
function withoutStartAndEnd(s: string, start: string, end: string): string | undefined {
|
||||
return startsWith(s, start) && endsWith(s, end) ? s.slice(start.length, s.length - end.length) : undefined;
|
||||
}
|
||||
|
||||
function removeLeadingDirectorySeparator(path: string): string {
|
||||
return path[0] === directorySeparator ? path.slice(1) : path;
|
||||
}
|
||||
|
||||
function getAmbientModuleCompletions(fragment: string, fragmentDirectory: string | undefined, checker: TypeChecker): ReadonlyArray<string> {
|
||||
// Get modules that the type checker picked up
|
||||
const ambientModules = checker.getAmbientModules().map(sym => stripQuotes(sym.name));
|
||||
const nonRelativeModuleNames = ambientModules.filter(moduleName => startsWith(moduleName, fragment));
|
||||
|
||||
// Nested modules of the form "module-name/sub" need to be adjusted to only return the string
|
||||
// after the last '/' that appears in the fragment because that's where the replacement span
|
||||
// starts
|
||||
if (fragmentDirectory !== undefined) {
|
||||
const moduleNameWithSeparator = ensureTrailingDirectorySeparator(fragmentDirectory);
|
||||
return nonRelativeModuleNames.map(nonRelativeModuleName => removePrefix(nonRelativeModuleName, moduleNameWithSeparator));
|
||||
}
|
||||
return nonRelativeModuleNames;
|
||||
}
|
||||
|
||||
function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): ReadonlyArray<PathCompletion> | undefined {
|
||||
const token = getTokenAtPosition(sourceFile, position);
|
||||
const commentRanges = getLeadingCommentRanges(sourceFile.text, token.pos);
|
||||
const range = commentRanges && find(commentRanges, commentRange => position >= commentRange.pos && position <= commentRange.end);
|
||||
if (!range) {
|
||||
return undefined;
|
||||
}
|
||||
const text = sourceFile.text.slice(range.pos, position);
|
||||
const match = tripleSlashDirectiveFragmentRegex.exec(text);
|
||||
if (!match) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const [, prefix, kind, toComplete] = match;
|
||||
const scriptPath = getDirectoryPath(sourceFile.path);
|
||||
const names = kind === "path" ? getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getExtensionOptions(compilerOptions, /*includeExtensions*/ true), host, sourceFile.path)
|
||||
: kind === "types" ? getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, getFragmentDirectory(toComplete), getExtensionOptions(compilerOptions))
|
||||
: Debug.fail();
|
||||
return addReplacementSpans(toComplete, range.pos + prefix.length, names);
|
||||
}
|
||||
|
||||
function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, fragmentDirectory: string | undefined, extensionOptions: ExtensionOptions, result: NameAndKind[] = []): ReadonlyArray<NameAndKind> {
|
||||
// Check for typings specified in compiler options
|
||||
const seen = createMap<true>();
|
||||
|
||||
const typeRoots = tryAndIgnoreErrors(() => getEffectiveTypeRoots(options, host)) || emptyArray;
|
||||
|
||||
for (const root of typeRoots) {
|
||||
getCompletionEntriesFromDirectories(root);
|
||||
}
|
||||
|
||||
// Also get all @types typings installed in visible node_modules directories
|
||||
for (const packageJson of findPackageJsons(scriptPath, host)) {
|
||||
const typesDir = combinePaths(getDirectoryPath(packageJson), "node_modules/@types");
|
||||
getCompletionEntriesFromDirectories(typesDir);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
function getCompletionEntriesFromDirectories(directory: string): void {
|
||||
if (!tryDirectoryExists(host, directory)) return;
|
||||
|
||||
for (const typeDirectoryName of tryGetDirectories(host, directory)) {
|
||||
const packageName = unmangleScopedPackageName(typeDirectoryName);
|
||||
if (options.types && !contains(options.types, packageName)) continue;
|
||||
|
||||
if (fragmentDirectory === undefined) {
|
||||
if (!seen.has(packageName)) {
|
||||
result.push(nameAndKind(packageName, ScriptElementKind.externalModuleName, /*extension*/ undefined));
|
||||
seen.set(packageName, true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const baseDirectory = combinePaths(directory, typeDirectoryName);
|
||||
const remainingFragment = tryRemoveDirectoryPrefix(fragmentDirectory, packageName, hostGetCanonicalFileName(host));
|
||||
if (remainingFragment !== undefined) {
|
||||
getCompletionEntriesForDirectoryFragment(remainingFragment, baseDirectory, extensionOptions, host, /*exclude*/ undefined, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findPackageJsons(directory: string, host: LanguageServiceHost): string[] {
|
||||
const paths: string[] = [];
|
||||
forEachAncestorDirectory(directory, ancestor => {
|
||||
const currentConfigPath = findConfigFile(ancestor, (f) => tryFileExists(host, f), "package.json");
|
||||
if (!currentConfigPath) {
|
||||
return true; // break out
|
||||
}
|
||||
paths.push(currentConfigPath);
|
||||
});
|
||||
return paths;
|
||||
}
|
||||
|
||||
function findPackageJson(directory: string, host: LanguageServiceHost): string | undefined {
|
||||
let packageJson: string | undefined;
|
||||
forEachAncestorDirectory(directory, ancestor => {
|
||||
if (ancestor === "node_modules") return true;
|
||||
packageJson = findConfigFile(ancestor, (f) => tryFileExists(host, f), "package.json");
|
||||
if (packageJson) {
|
||||
return true; // break out
|
||||
}
|
||||
});
|
||||
return packageJson;
|
||||
}
|
||||
|
||||
function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string): ReadonlyArray<string> {
|
||||
if (!host.readFile || !host.fileExists) return emptyArray;
|
||||
|
||||
const result: string[] = [];
|
||||
for (const packageJson of findPackageJsons(scriptPath, host)) {
|
||||
const contents = readJson(packageJson, host as { readFile: (filename: string) => string | undefined }); // Cast to assert that readFile is defined
|
||||
// Provide completions for all non @types dependencies
|
||||
for (const key of nodeModulesDependencyKeys) {
|
||||
const dependencies: object | undefined = (contents as any)[key];
|
||||
if (!dependencies) continue;
|
||||
for (const dep in dependencies) {
|
||||
if (dependencies.hasOwnProperty(dep) && !startsWith(dep, "@types/")) {
|
||||
result.push(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Replace everything after the last directory separator that appears
|
||||
function getDirectoryFragmentTextSpan(text: string, textStart: number): TextSpan | undefined {
|
||||
const index = Math.max(text.lastIndexOf(directorySeparator), text.lastIndexOf("\\"));
|
||||
const offset = index !== -1 ? index + 1 : 0;
|
||||
// If the range is an identifier, span is unnecessary.
|
||||
const length = text.length - offset;
|
||||
return length === 0 || isIdentifierText(text.substr(offset, length), ScriptTarget.ESNext) ? undefined : createTextSpan(textStart + offset, length);
|
||||
}
|
||||
|
||||
// Returns true if the path is explicitly relative to the script (i.e. relative to . or ..)
|
||||
function isPathRelativeToScript(path: string) {
|
||||
if (path && path.length >= 2 && path.charCodeAt(0) === CharacterCodes.dot) {
|
||||
const slashIndex = path.length >= 3 && path.charCodeAt(1) === CharacterCodes.dot ? 2 : 1;
|
||||
const slashCharCode = path.charCodeAt(slashIndex);
|
||||
return slashCharCode === CharacterCodes.slash || slashCharCode === CharacterCodes.backslash;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches a triple slash reference directive with an incomplete string literal for its path. Used
|
||||
* to determine if the caret is currently within the string literal and capture the literal fragment
|
||||
* for completions.
|
||||
* For example, this matches
|
||||
*
|
||||
* /// <reference path="fragment
|
||||
*
|
||||
* but not
|
||||
*
|
||||
* /// <reference path="fragment"
|
||||
*/
|
||||
const tripleSlashDirectiveFragmentRegex = /^(\/\/\/\s*<reference\s+(path|types)\s*=\s*(?:'|"))([^\3"]*)$/;
|
||||
|
||||
const nodeModulesDependencyKeys: ReadonlyArray<string> = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
|
||||
|
||||
function tryGetDirectories(host: LanguageServiceHost, directoryName: string): string[] {
|
||||
return tryIOAndConsumeErrors(host, host.getDirectories, directoryName) || [];
|
||||
}
|
||||
|
||||
function tryReadDirectory(host: LanguageServiceHost, path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>): ReadonlyArray<string> {
|
||||
return tryIOAndConsumeErrors(host, host.readDirectory, path, extensions, exclude, include) || emptyArray;
|
||||
}
|
||||
|
||||
function tryFileExists(host: LanguageServiceHost, path: string): boolean {
|
||||
return tryIOAndConsumeErrors(host, host.fileExists, path);
|
||||
}
|
||||
|
||||
function tryDirectoryExists(host: LanguageServiceHost, path: string): boolean {
|
||||
return tryAndIgnoreErrors(() => directoryProbablyExists(path, host)) || false;
|
||||
}
|
||||
|
||||
function tryIOAndConsumeErrors<T>(host: LanguageServiceHost, toApply: ((...a: any[]) => T) | undefined, ...args: any[]) {
|
||||
return tryAndIgnoreErrors(() => toApply && toApply.apply(host, args));
|
||||
}
|
||||
|
||||
function tryAndIgnoreErrors<T>(cb: () => T): T | undefined {
|
||||
try { return cb(); }
|
||||
catch { return undefined; }
|
||||
}
|
||||
|
||||
function containsSlash(fragment: string) {
|
||||
return stringContains(fragment, directorySeparator);
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ namespace ts {
|
||||
switch (statement.kind) {
|
||||
case SyntaxKind.VariableStatement:
|
||||
return (statement as VariableStatement).declarationList.declarations.some(decl =>
|
||||
isRequireCall(propertyAccessLeftHandSide(decl.initializer!), /*checkArgumentIsStringLiteralLike*/ true)); // TODO: GH#18217
|
||||
!!decl.initializer && isRequireCall(propertyAccessLeftHandSide(decl.initializer), /*checkArgumentIsStringLiteralLike*/ true));
|
||||
case SyntaxKind.ExpressionStatement: {
|
||||
const { expression } = statement as ExpressionStatement;
|
||||
if (!isBinaryExpression(expression)) return isRequireCall(expression, /*checkArgumentIsStringLiteralLike*/ true);
|
||||
@@ -113,59 +113,39 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function addConvertToAsyncFunctionDiagnostics(node: FunctionLikeDeclaration, checker: TypeChecker, diags: DiagnosticWithLocation[]): void {
|
||||
|
||||
if (isAsyncFunction(node) || !node.body) {
|
||||
return;
|
||||
function addConvertToAsyncFunctionDiagnostics(node: FunctionLikeDeclaration, checker: TypeChecker, diags: Push<DiagnosticWithLocation>): void {
|
||||
if (!isAsyncFunction(node) &&
|
||||
node.body &&
|
||||
isBlock(node.body) &&
|
||||
hasReturnStatementWithPromiseHandler(node.body) &&
|
||||
returnsPromise(node, checker)) {
|
||||
diags.push(createDiagnosticForNode(
|
||||
!node.name && isVariableDeclaration(node.parent) && isIdentifier(node.parent.name) ? node.parent.name : node,
|
||||
Diagnostics.This_may_be_converted_to_an_async_function));
|
||||
}
|
||||
}
|
||||
|
||||
function returnsPromise(node: FunctionLikeDeclaration, checker: TypeChecker): boolean {
|
||||
const functionType = checker.getTypeAtLocation(node);
|
||||
|
||||
const callSignatures = checker.getSignaturesOfType(functionType, SignatureKind.Call);
|
||||
const returnType = callSignatures.length ? checker.getReturnTypeOfSignature(callSignatures[0]) : undefined;
|
||||
|
||||
if (!returnType || !checker.getPromisedTypeOfPromise(returnType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// collect all the return statements
|
||||
// check that a property access expression exists in there and that it is a handler
|
||||
const returnStatements = getReturnStatementsWithPromiseHandlers(node);
|
||||
if (returnStatements.length > 0) {
|
||||
diags.push(createDiagnosticForNode(!node.name && isVariableDeclaration(node.parent) && isIdentifier(node.parent.name) ? node.parent.name : node, Diagnostics.This_may_be_converted_to_an_async_function));
|
||||
}
|
||||
return !!returnType && !!checker.getPromisedTypeOfPromise(returnType);
|
||||
}
|
||||
|
||||
function getErrorNodeFromCommonJsIndicator(commonJsModuleIndicator: Node): Node {
|
||||
return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getReturnStatementsWithPromiseHandlers(node: Node): ReturnStatement[] {
|
||||
const returnStatements: ReturnStatement[] = [];
|
||||
if (isFunctionLike(node)) {
|
||||
forEachChild(node, visit);
|
||||
}
|
||||
else {
|
||||
visit(node);
|
||||
}
|
||||
function hasReturnStatementWithPromiseHandler(body: Block): boolean {
|
||||
return !!forEachReturnStatement(body, isReturnStatementWithFixablePromiseHandler);
|
||||
}
|
||||
|
||||
function visit(child: Node) {
|
||||
if (isFunctionLike(child)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isReturnStatement(child) && child.expression && isFixablePromiseHandler(child.expression)) {
|
||||
returnStatements.push(child);
|
||||
}
|
||||
|
||||
forEachChild(child, visit);
|
||||
}
|
||||
return returnStatements;
|
||||
export function isReturnStatementWithFixablePromiseHandler(node: Node): node is ReturnStatement {
|
||||
return isReturnStatement(node) && !!node.expression && isFixablePromiseHandler(node.expression);
|
||||
}
|
||||
|
||||
// Should be kept up to date with transformExpression in convertToAsyncFunction.ts
|
||||
function isFixablePromiseHandler(node: Node): boolean {
|
||||
export function isFixablePromiseHandler(node: Node): boolean {
|
||||
// ensure outermost call exists and is a promise handler
|
||||
if (!isPromiseHandler(node) || !node.arguments.every(isFixablePromiseArgument)) {
|
||||
return false;
|
||||
|
||||
+28
-21
@@ -276,11 +276,15 @@ namespace ts.textChanges {
|
||||
return this.replaceRangeWithNodes(sourceFile, getAdjustedRange(sourceFile, oldNode, oldNode, options), newNodes, options);
|
||||
}
|
||||
|
||||
public replaceNodeWithText(sourceFile: SourceFile, oldNode: Node, text: string): void {
|
||||
this.replaceRangeWithText(sourceFile, getAdjustedRange(sourceFile, oldNode, oldNode, useNonAdjustedPositions), text);
|
||||
}
|
||||
|
||||
public replaceNodeRangeWithNodes(sourceFile: SourceFile, startNode: Node, endNode: Node, newNodes: ReadonlyArray<Node>, options: ReplaceWithMultipleNodesOptions & ConfigurableStartEnd = useNonAdjustedPositions) {
|
||||
return this.replaceRangeWithNodes(sourceFile, getAdjustedRange(sourceFile, startNode, endNode, options), newNodes, options);
|
||||
}
|
||||
|
||||
private nextCommaToken (sourceFile: SourceFile, node: Node): Node | undefined {
|
||||
private nextCommaToken(sourceFile: SourceFile, node: Node): Node | undefined {
|
||||
const next = findNextToken(node, node.parent, sourceFile);
|
||||
return next && next.kind === SyntaxKind.CommaToken ? next : undefined;
|
||||
}
|
||||
@@ -339,6 +343,21 @@ namespace ts.textChanges {
|
||||
this.insertText(sourceFile, token.getStart(sourceFile), text);
|
||||
}
|
||||
|
||||
public insertJsdocCommentBefore(sourceFile: SourceFile, node: HasJSDoc, tag: JSDoc) {
|
||||
const fnStart = node.getStart(sourceFile);
|
||||
if (node.jsDoc) {
|
||||
for (const jsdoc of node.jsDoc) {
|
||||
this.deleteRange(sourceFile, {
|
||||
pos: getLineStartPositionForPosition(jsdoc.getStart(sourceFile), sourceFile),
|
||||
end: getAdjustedEndPosition(sourceFile, jsdoc, /*options*/ {})
|
||||
});
|
||||
}
|
||||
}
|
||||
const startPosition = getPrecedingNonSpaceCharacterPosition(sourceFile.text, fnStart - 1);
|
||||
const indent = sourceFile.text.slice(startPosition, fnStart);
|
||||
this.insertNodeAt(sourceFile, fnStart, tag, { preserveLeadingWhitespace: false, suffix: this.newLineCharacter + indent });
|
||||
}
|
||||
|
||||
public replaceRangeWithText(sourceFile: SourceFile, range: TextRange, text: string) {
|
||||
this.changes.push({ kind: ChangeKind.Text, sourceFile, range, text });
|
||||
}
|
||||
@@ -675,7 +694,7 @@ namespace ts.textChanges {
|
||||
}
|
||||
|
||||
private finishDeleteDeclarations(): void {
|
||||
const deletedNodesInLists = new NodeSet(); // Stores ids of nodes in lists that we already deleted. Used to avoid deleting `, ` twice in `a, b`.
|
||||
const deletedNodesInLists = new NodeSet(); // Stores nodes in lists that we already deleted. Used to avoid deleting `, ` twice in `a, b`.
|
||||
for (const { sourceFile, node } of this.deletedNodes) {
|
||||
if (!this.deletedNodes.some(d => d.sourceFile === sourceFile && rangeContainsRangeExclusive(d.node, node))) {
|
||||
if (isArray(node)) {
|
||||
@@ -766,7 +785,7 @@ namespace ts.textChanges {
|
||||
const nonFormattedText = statements.map(s => getNonformattedText(s, oldFile, newLineCharacter).text).join(newLineCharacter);
|
||||
const sourceFile = createSourceFile("any file name", nonFormattedText, ScriptTarget.ESNext, /*setParentNodes*/ true, scriptKind);
|
||||
const changes = formatting.formatDocument(sourceFile, formatContext);
|
||||
return applyChanges(nonFormattedText, changes);
|
||||
return applyChanges(nonFormattedText, changes) + newLineCharacter;
|
||||
}
|
||||
|
||||
function computeNewText(change: Change, sourceFile: SourceFile, newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): string {
|
||||
@@ -806,7 +825,7 @@ namespace ts.textChanges {
|
||||
}
|
||||
|
||||
/** Note: output node may be mutated input node. */
|
||||
function getNonformattedText(node: Node, sourceFile: SourceFile | undefined, newLineCharacter: string): { text: string, node: Node } {
|
||||
export function getNonformattedText(node: Node, sourceFile: SourceFile | undefined, newLineCharacter: string): { text: string, node: Node } {
|
||||
const writer = new Writer(newLineCharacter);
|
||||
const newLine = newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed;
|
||||
createPrinter({ newLine, neverAsciiEscape: true }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer);
|
||||
@@ -1029,7 +1048,7 @@ namespace ts.textChanges {
|
||||
}
|
||||
|
||||
export function isValidLocationToAddComment(sourceFile: SourceFile, position: number) {
|
||||
return !isInComment(sourceFile, position) && !isInString(sourceFile, position) && !isInTemplateString(sourceFile, position);
|
||||
return !isInComment(sourceFile, position) && !isInString(sourceFile, position) && !isInTemplateString(sourceFile, position) && !isInJSXText(sourceFile, position);
|
||||
}
|
||||
|
||||
function needSemicolonBetween(a: Node, b: Node): boolean {
|
||||
@@ -1042,25 +1061,13 @@ namespace ts.textChanges {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.Parameter: {
|
||||
const oldFunction = node.parent;
|
||||
if (isArrowFunction(oldFunction) && oldFunction.parameters.length === 1) {
|
||||
if (isArrowFunction(oldFunction) &&
|
||||
oldFunction.parameters.length === 1 &&
|
||||
!findChildOfKind(oldFunction, SyntaxKind.OpenParenToken, sourceFile)) {
|
||||
// Lambdas with exactly one parameter are special because, after removal, there
|
||||
// must be an empty parameter list (i.e. `()`) and this won't necessarily be the
|
||||
// case if the parameter is simply removed (e.g. in `x => 1`).
|
||||
const newFunction = updateArrowFunction(
|
||||
oldFunction,
|
||||
oldFunction.modifiers,
|
||||
oldFunction.typeParameters,
|
||||
/*parameters*/ undefined!, // TODO: GH#18217
|
||||
oldFunction.type,
|
||||
oldFunction.equalsGreaterThanToken,
|
||||
oldFunction.body);
|
||||
|
||||
// Drop leading and trailing trivia of the new function because we're only going
|
||||
// to replace the span (vs the full span) of the old function - the old leading
|
||||
// and trailing trivia will remain.
|
||||
suppressLeadingAndTrailingTrivia(newFunction);
|
||||
|
||||
changes.replaceNode(sourceFile, oldFunction, newFunction);
|
||||
changes.replaceNodeWithText(sourceFile, node, "()");
|
||||
}
|
||||
else {
|
||||
deleteNodeInList(changes, deletedNodesInLists, sourceFile, node);
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"types.ts",
|
||||
"utilities.ts",
|
||||
"classifier.ts",
|
||||
"pathCompletions.ts",
|
||||
"stringCompletions.ts",
|
||||
"completions.ts",
|
||||
"documentHighlights.ts",
|
||||
"documentRegistry.ts",
|
||||
@@ -45,6 +45,7 @@
|
||||
"refactorProvider.ts",
|
||||
"codefixes/addMissingInvocationForDecorator.ts",
|
||||
"codefixes/annotateWithTypeFromJSDoc.ts",
|
||||
"codefixes/inferFromUsage.ts",
|
||||
"codefixes/convertFunctionToEs6Class.ts",
|
||||
"codefixes/convertToAsyncFunction.ts",
|
||||
"codefixes/convertToEs6Module.ts",
|
||||
@@ -66,7 +67,6 @@
|
||||
"codefixes/fixAwaitInSyncFunction.ts",
|
||||
"codefixes/disableJsDiagnostics.ts",
|
||||
"codefixes/helpers.ts",
|
||||
"codefixes/inferFromUsage.ts",
|
||||
"codefixes/fixInvalidImportSyntax.ts",
|
||||
"codefixes/fixStrictClassInitialization.ts",
|
||||
"codefixes/generateTypes.ts",
|
||||
|
||||
+63
-49
@@ -212,9 +212,9 @@ namespace ts {
|
||||
*
|
||||
* If this is implemented, `getResolvedModuleWithFailedLookupLocationsFromCache` should be too.
|
||||
*/
|
||||
resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[];
|
||||
resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference): (ResolvedModule | undefined)[];
|
||||
getResolvedModuleWithFailedLookupLocationsFromCache?(modulename: string, containingFile: string): ResolvedModuleWithFailedLookupLocations | undefined;
|
||||
resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
|
||||
resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[];
|
||||
/* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution;
|
||||
/* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean;
|
||||
|
||||
@@ -238,6 +238,8 @@ namespace ts {
|
||||
/* @internal */
|
||||
export const emptyOptions = {};
|
||||
|
||||
export type WithMetadata<T> = T & { metadata?: unknown; };
|
||||
|
||||
//
|
||||
// Public services of a language service instance associated
|
||||
// with a language service host instance
|
||||
@@ -268,7 +270,7 @@ namespace ts {
|
||||
getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications;
|
||||
getEncodedSemanticClassifications(fileName: string, span: TextSpan): Classifications;
|
||||
|
||||
getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions | undefined): CompletionInfo | undefined;
|
||||
getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions | undefined): WithMetadata<CompletionInfo> | undefined;
|
||||
// "options" and "source" are optional only for backwards-compatibility
|
||||
getCompletionEntryDetails(
|
||||
fileName: string,
|
||||
@@ -289,19 +291,19 @@ namespace ts {
|
||||
getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): SignatureHelpItems | undefined;
|
||||
|
||||
getRenameInfo(fileName: string, position: number): RenameInfo;
|
||||
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] | undefined;
|
||||
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): ReadonlyArray<RenameLocation> | undefined;
|
||||
|
||||
getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] | undefined;
|
||||
getDefinitionAtPosition(fileName: string, position: number): ReadonlyArray<DefinitionInfo> | undefined;
|
||||
getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined;
|
||||
getTypeDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] | undefined;
|
||||
getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] | undefined;
|
||||
getTypeDefinitionAtPosition(fileName: string, position: number): ReadonlyArray<DefinitionInfo> | undefined;
|
||||
getImplementationAtPosition(fileName: string, position: number): ReadonlyArray<ImplementationLocation> | undefined;
|
||||
|
||||
getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined;
|
||||
findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined;
|
||||
getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[] | undefined;
|
||||
|
||||
/** @deprecated */
|
||||
getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined;
|
||||
getOccurrencesAtPosition(fileName: string, position: number): ReadonlyArray<ReferenceEntry> | undefined;
|
||||
|
||||
getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles?: boolean): NavigateToItem[];
|
||||
getNavigationBarItems(fileName: string): NavigationBarItem[];
|
||||
@@ -712,49 +714,51 @@ namespace ts {
|
||||
}
|
||||
|
||||
export interface FormatCodeSettings extends EditorSettings {
|
||||
insertSpaceAfterCommaDelimiter?: boolean;
|
||||
insertSpaceAfterSemicolonInForStatements?: boolean;
|
||||
insertSpaceBeforeAndAfterBinaryOperators?: boolean;
|
||||
insertSpaceAfterConstructor?: boolean;
|
||||
insertSpaceAfterKeywordsInControlFlowStatements?: boolean;
|
||||
insertSpaceAfterFunctionKeywordForAnonymousFunctions?: boolean;
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis?: boolean;
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets?: boolean;
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean;
|
||||
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces?: boolean;
|
||||
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean;
|
||||
insertSpaceAfterTypeAssertion?: boolean;
|
||||
insertSpaceBeforeFunctionParenthesis?: boolean;
|
||||
placeOpenBraceOnNewLineForFunctions?: boolean;
|
||||
placeOpenBraceOnNewLineForControlBlocks?: boolean;
|
||||
insertSpaceBeforeTypeAnnotation?: boolean;
|
||||
indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean;
|
||||
readonly insertSpaceAfterCommaDelimiter?: boolean;
|
||||
readonly insertSpaceAfterSemicolonInForStatements?: boolean;
|
||||
readonly insertSpaceBeforeAndAfterBinaryOperators?: boolean;
|
||||
readonly insertSpaceAfterConstructor?: boolean;
|
||||
readonly insertSpaceAfterKeywordsInControlFlowStatements?: boolean;
|
||||
readonly insertSpaceAfterFunctionKeywordForAnonymousFunctions?: boolean;
|
||||
readonly insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis?: boolean;
|
||||
readonly insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets?: boolean;
|
||||
readonly insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean;
|
||||
readonly insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces?: boolean;
|
||||
readonly insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean;
|
||||
readonly insertSpaceAfterTypeAssertion?: boolean;
|
||||
readonly insertSpaceBeforeFunctionParenthesis?: boolean;
|
||||
readonly placeOpenBraceOnNewLineForFunctions?: boolean;
|
||||
readonly placeOpenBraceOnNewLineForControlBlocks?: boolean;
|
||||
readonly insertSpaceBeforeTypeAnnotation?: boolean;
|
||||
readonly indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean;
|
||||
}
|
||||
|
||||
export function getDefaultFormatCodeSettings(newLineCharacter?: string): FormatCodeSettings {
|
||||
return {
|
||||
indentSize: 4,
|
||||
tabSize: 4,
|
||||
newLineCharacter: newLineCharacter || "\n",
|
||||
convertTabsToSpaces: true,
|
||||
indentStyle: IndentStyle.Smart,
|
||||
insertSpaceAfterConstructor: false,
|
||||
insertSpaceAfterCommaDelimiter: true,
|
||||
insertSpaceAfterSemicolonInForStatements: true,
|
||||
insertSpaceBeforeAndAfterBinaryOperators: true,
|
||||
insertSpaceAfterKeywordsInControlFlowStatements: true,
|
||||
insertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
|
||||
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
|
||||
insertSpaceBeforeFunctionParenthesis: false,
|
||||
placeOpenBraceOnNewLineForFunctions: false,
|
||||
placeOpenBraceOnNewLineForControlBlocks: false,
|
||||
};
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export const testFormatSettings: FormatCodeSettings = {
|
||||
baseIndentSize: 0,
|
||||
indentSize: 4,
|
||||
tabSize: 4,
|
||||
newLineCharacter: "\n",
|
||||
convertTabsToSpaces: true,
|
||||
indentStyle: IndentStyle.Smart,
|
||||
insertSpaceAfterCommaDelimiter: true,
|
||||
insertSpaceAfterSemicolonInForStatements: true,
|
||||
insertSpaceBeforeAndAfterBinaryOperators: true,
|
||||
insertSpaceAfterConstructor: false,
|
||||
insertSpaceAfterKeywordsInControlFlowStatements: true,
|
||||
insertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
|
||||
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
|
||||
insertSpaceAfterTypeAssertion: false,
|
||||
placeOpenBraceOnNewLineForFunctions: false,
|
||||
placeOpenBraceOnNewLineForControlBlocks: false,
|
||||
insertSpaceBeforeTypeAnnotation: false
|
||||
};
|
||||
export const testFormatSettings = getDefaultFormatCodeSettings("\n");
|
||||
|
||||
export interface DefinitionInfo extends DocumentSpan {
|
||||
kind: ScriptElementKind;
|
||||
@@ -976,6 +980,7 @@ namespace ts {
|
||||
Whitespace,
|
||||
Identifier,
|
||||
NumberLiteral,
|
||||
BigIntLiteral,
|
||||
StringLiteral,
|
||||
RegExpLiteral,
|
||||
}
|
||||
@@ -1124,7 +1129,14 @@ namespace ts {
|
||||
ambientModifier = "declare",
|
||||
staticModifier = "static",
|
||||
abstractModifier = "abstract",
|
||||
optionalModifier = "optional"
|
||||
optionalModifier = "optional",
|
||||
|
||||
dtsModifier = ".d.ts",
|
||||
tsModifier = ".ts",
|
||||
tsxModifier = ".tsx",
|
||||
jsModifier = ".js",
|
||||
jsxModifier = ".jsx",
|
||||
jsonModifier = ".json",
|
||||
}
|
||||
|
||||
export const enum ClassificationTypeNames {
|
||||
@@ -1132,6 +1144,7 @@ namespace ts {
|
||||
identifier = "identifier",
|
||||
keyword = "keyword",
|
||||
numericLiteral = "number",
|
||||
bigintLiteral = "bigint",
|
||||
operator = "operator",
|
||||
stringLiteral = "string",
|
||||
whiteSpace = "whitespace",
|
||||
@@ -1180,5 +1193,6 @@ namespace ts {
|
||||
jsxAttribute = 22,
|
||||
jsxText = 23,
|
||||
jsxAttributeStringLiteralValue = 24,
|
||||
bigintLiteral = 25,
|
||||
}
|
||||
}
|
||||
|
||||
+93
-15
@@ -368,6 +368,9 @@ namespace ts {
|
||||
const kind = getAssignmentDeclarationKind(node as BinaryExpression);
|
||||
const { right } = node as BinaryExpression;
|
||||
switch (kind) {
|
||||
case AssignmentDeclarationKind.ObjectDefinePropertyValue:
|
||||
case AssignmentDeclarationKind.ObjectDefinePropertyExports:
|
||||
case AssignmentDeclarationKind.ObjectDefinePrototypeProperty:
|
||||
case AssignmentDeclarationKind.None:
|
||||
return ScriptElementKind.unknown;
|
||||
case AssignmentDeclarationKind.ExportsProperty:
|
||||
@@ -906,6 +909,20 @@ namespace ts {
|
||||
return isTemplateLiteralKind(token.kind) && position > token.getStart(sourceFile);
|
||||
}
|
||||
|
||||
export function isInJSXText(sourceFile: SourceFile, position: number) {
|
||||
const token = getTokenAtPosition(sourceFile, position);
|
||||
if (isJsxText(token)) {
|
||||
return true;
|
||||
}
|
||||
if (token.kind === SyntaxKind.OpenBraceToken && isJsxExpression(token.parent) && isJsxElement(token.parent.parent)) {
|
||||
return true;
|
||||
}
|
||||
if (token.kind === SyntaxKind.LessThanToken && isJsxOpeningLikeElement(token.parent) && isJsxElement(token.parent.parent)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function findPrecedingMatchingToken(token: Node, matchingTokenKind: SyntaxKind, sourceFile: SourceFile) {
|
||||
const tokenKind = token.kind;
|
||||
let remainingMatchingTokens = 0;
|
||||
@@ -1011,6 +1028,7 @@ namespace ts {
|
||||
case SyntaxKind.Identifier:
|
||||
case SyntaxKind.StringLiteral:
|
||||
case SyntaxKind.NumericLiteral:
|
||||
case SyntaxKind.BigIntLiteral:
|
||||
case SyntaxKind.TrueKeyword:
|
||||
case SyntaxKind.FalseKeyword:
|
||||
|
||||
@@ -1334,15 +1352,9 @@ namespace ts {
|
||||
!bindingElement.propertyName;
|
||||
}
|
||||
|
||||
export function getPropertySymbolFromBindingElement(checker: TypeChecker, bindingElement: ObjectBindingElementWithoutPropertyName) {
|
||||
export function getPropertySymbolFromBindingElement(checker: TypeChecker, bindingElement: ObjectBindingElementWithoutPropertyName): Symbol | undefined {
|
||||
const typeOfPattern = checker.getTypeAtLocation(bindingElement.parent);
|
||||
const propSymbol = typeOfPattern && checker.getPropertyOfType(typeOfPattern, bindingElement.name.text);
|
||||
if (propSymbol && propSymbol.flags & SymbolFlags.Accessor) {
|
||||
// See GH#16922
|
||||
Debug.assert(!!(propSymbol.flags & SymbolFlags.Transient));
|
||||
return (propSymbol as TransientSymbol).target;
|
||||
}
|
||||
return propSymbol;
|
||||
return typeOfPattern && checker.getPropertyOfType(typeOfPattern, bindingElement.name.text);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1398,7 +1410,6 @@ namespace ts {
|
||||
return node.modifiers && find(node.modifiers, m => m.kind === kind);
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function insertImport(changes: textChanges.ChangeTracker, sourceFile: SourceFile, importDecl: Statement): void {
|
||||
const lastImportDeclaration = findLast(sourceFile.statements, isAnyImportSyntax);
|
||||
if (lastImportDeclaration) {
|
||||
@@ -1587,7 +1598,6 @@ namespace ts {
|
||||
return displayPart("\n", SymbolDisplayPartKind.lineBreak);
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function mapToDisplayParts(writeDisplayParts: (writer: DisplayPartsSymbolWriter) => void): SymbolDisplayPart[] {
|
||||
try {
|
||||
writeDisplayParts(displayPartWriter);
|
||||
@@ -1662,6 +1672,13 @@ namespace ts {
|
||||
return position;
|
||||
}
|
||||
|
||||
export function getPrecedingNonSpaceCharacterPosition(text: string, position: number) {
|
||||
while (position > -1 && isWhiteSpaceSingleLine(text.charCodeAt(position))) {
|
||||
position -= 1;
|
||||
}
|
||||
return position + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a deep, memberwise clone of a node with no source map location.
|
||||
*
|
||||
@@ -1733,7 +1750,6 @@ namespace ts {
|
||||
/**
|
||||
* Sets EmitFlags to suppress leading and trailing trivia on the node.
|
||||
*/
|
||||
/* @internal */
|
||||
export function suppressLeadingAndTrailingTrivia(node: Node) {
|
||||
suppressLeadingTrivia(node);
|
||||
suppressTrailingTrivia(node);
|
||||
@@ -1742,7 +1758,6 @@ namespace ts {
|
||||
/**
|
||||
* Sets EmitFlags to suppress leading trivia on the node.
|
||||
*/
|
||||
/* @internal */
|
||||
export function suppressLeadingTrivia(node: Node) {
|
||||
addEmitFlagsRecursively(node, EmitFlags.NoLeadingComments, getFirstChild);
|
||||
}
|
||||
@@ -1750,7 +1765,6 @@ namespace ts {
|
||||
/**
|
||||
* Sets EmitFlags to suppress trailing trivia on the node.
|
||||
*/
|
||||
/* @internal */
|
||||
export function suppressTrailingTrivia(node: Node) {
|
||||
addEmitFlagsRecursively(node, EmitFlags.NoTrailingComments, getLastChild);
|
||||
}
|
||||
@@ -1765,7 +1779,6 @@ namespace ts {
|
||||
return node.forEachChild(child => child);
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function getUniqueName(baseName: string, sourceFile: SourceFile): string {
|
||||
let nameText = baseName;
|
||||
for (let i = 1; !isFileLevelUniqueName(sourceFile, nameText); i++) {
|
||||
@@ -1779,7 +1792,6 @@ namespace ts {
|
||||
* to be on the reference, rather than the declaration, because it's closer to where the
|
||||
* user was before extracting it.
|
||||
*/
|
||||
/* @internal */
|
||||
export function getRenameLocation(edits: ReadonlyArray<FileTextChanges>, renameFilename: string, name: string, preferLastLocation: boolean): number {
|
||||
let delta = 0;
|
||||
let lastPos = -1;
|
||||
@@ -1830,4 +1842,70 @@ namespace ts {
|
||||
if (idx === -1) idx = change.indexOf('"' + name);
|
||||
return idx === -1 ? -1 : idx + 1;
|
||||
}
|
||||
|
||||
export function getContextualTypeFromParent(node: Expression, checker: TypeChecker): Type | undefined {
|
||||
const { parent } = node;
|
||||
switch (parent.kind) {
|
||||
case SyntaxKind.NewExpression:
|
||||
return checker.getContextualType(parent as NewExpression);
|
||||
case SyntaxKind.BinaryExpression: {
|
||||
const { left, operatorToken, right } = parent as BinaryExpression;
|
||||
return isEqualityOperatorKind(operatorToken.kind)
|
||||
? checker.getTypeAtLocation(node === right ? left : right)
|
||||
: checker.getContextualType(node);
|
||||
}
|
||||
case SyntaxKind.CaseClause:
|
||||
return (parent as CaseClause).expression === node ? getSwitchedType(parent as CaseClause, checker) : undefined;
|
||||
default:
|
||||
return checker.getContextualType(node);
|
||||
}
|
||||
}
|
||||
|
||||
export function quote(text: string, preferences: UserPreferences): string {
|
||||
if (/^\d+$/.test(text)) {
|
||||
return text;
|
||||
}
|
||||
const quoted = JSON.stringify(text);
|
||||
switch (preferences.quotePreference) {
|
||||
case undefined:
|
||||
case "double":
|
||||
return quoted;
|
||||
case "single":
|
||||
return `'${stripQuotes(quoted).replace("'", "\\'").replace('\\"', '"')}'`;
|
||||
default:
|
||||
return Debug.assertNever(preferences.quotePreference);
|
||||
}
|
||||
}
|
||||
|
||||
export function isEqualityOperatorKind(kind: SyntaxKind): kind is EqualityOperator {
|
||||
switch (kind) {
|
||||
case SyntaxKind.EqualsEqualsEqualsToken:
|
||||
case SyntaxKind.EqualsEqualsToken:
|
||||
case SyntaxKind.ExclamationEqualsEqualsToken:
|
||||
case SyntaxKind.ExclamationEqualsToken:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function isStringLiteralOrTemplate(node: Node): node is StringLiteralLike | TemplateExpression | TaggedTemplateExpression {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.StringLiteral:
|
||||
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
||||
case SyntaxKind.TemplateExpression:
|
||||
case SyntaxKind.TaggedTemplateExpression:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function hasIndexSignature(type: Type): boolean {
|
||||
return !!type.getStringIndexType() || !!type.getNumberIndexType();
|
||||
}
|
||||
|
||||
export function getSwitchedType(caseClause: CaseClause, checker: TypeChecker): Type | undefined {
|
||||
return checker.getTypeAtLocation(caseClause.parent.parent.expression);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user