Merge branch 'master' into dev/aozgaa/cSharpObjLiteralFormatting

This commit is contained in:
Arthur Ozga
2018-03-29 16:10:33 -07:00
332 changed files with 87849 additions and 72553 deletions
+19
View File
@@ -27,6 +27,25 @@ namespace ts {
const codeFixRegistrations: CodeFixRegistration[][] = [];
const fixIdToRegistration = createMap<CodeFixRegistration>();
type DiagnosticAndArguments = DiagnosticMessage | [DiagnosticMessage, string] | [DiagnosticMessage, string, string];
function diagnosticToString(diag: DiagnosticAndArguments): string {
return isArray(diag)
? formatStringFromArgs(getLocaleSpecificMessage(diag[0]), diag.slice(1) as ReadonlyArray<string>)
: getLocaleSpecificMessage(diag);
}
export function createCodeFixActionNoFixId(changes: FileTextChanges[], description: DiagnosticAndArguments) {
return createCodeFixActionWorker(diagnosticToString(description), changes, /*fixId*/ undefined, /*fixAllDescription*/ undefined);
}
export function createCodeFixAction(changes: FileTextChanges[], description: DiagnosticAndArguments, fixId: {}, fixAllDescription: DiagnosticAndArguments, command?: CodeActionCommand): CodeFixAction {
return createCodeFixActionWorker(diagnosticToString(description), changes, fixId, diagnosticToString(fixAllDescription), command);
}
function createCodeFixActionWorker(description: string, changes: FileTextChanges[], fixId?: {}, fixAllDescription?: string, command?: CodeActionCommand): CodeFixAction {
return { description, changes, fixId, fixAllDescription, commands: command ? [command] : undefined };
}
export function registerCodeFix(reg: CodeFixRegistration) {
for (const error of reg.errorCodes) {
let registrations = codeFixRegistrations[error];
@@ -6,7 +6,7 @@ namespace ts.codefix {
errorCodes,
getCodeActions: (context) => {
const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span.start));
return [{ description: getLocaleSpecificMessage(Diagnostics.Call_decorator_expression), changes, fixId }];
return [createCodeFixAction(changes, Diagnostics.Call_decorator_expression, fixId, Diagnostics.Add_to_all_uncalled_decorators)];
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file!, diag.start!)),
@@ -7,9 +7,8 @@ namespace ts.codefix {
getCodeActions(context) {
const decl = getDeclaration(context.sourceFile, context.span.start);
if (!decl) return;
const description = getLocaleSpecificMessage(Diagnostics.Annotate_with_type_from_JSDoc);
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, decl));
return [{ description, changes, fixId }];
return [createCodeFixAction(changes, Diagnostics.Annotate_with_type_from_JSDoc, fixId, Diagnostics.Annotate_everything_with_types_from_JSDoc)];
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
@@ -6,7 +6,7 @@ namespace ts.codefix {
errorCodes,
getCodeActions(context: CodeFixContext) {
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, context.span.start, context.program.getTypeChecker()));
return [{ description: getLocaleSpecificMessage(Diagnostics.Convert_function_to_an_ES2015_class), changes, fixId }];
return [createCodeFixAction(changes, Diagnostics.Convert_function_to_an_ES2015_class, fixId, Diagnostics.Convert_all_constructor_functions_to_classes)];
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => doChange(changes, err.file!, err.start, context.program.getTypeChecker())),
+14 -16
View File
@@ -3,7 +3,6 @@ namespace ts.codefix {
registerCodeFix({
errorCodes: [Diagnostics.File_is_a_CommonJS_module_it_may_be_converted_to_an_ES6_module.code],
getCodeActions(context) {
const description = getLocaleSpecificMessage(Diagnostics.Convert_to_ES6_module);
const { sourceFile, program } = context;
const changes = textChanges.ChangeTracker.with(context, changes => {
const moduleExportsChangedToDefault = convertFileToEs6Module(sourceFile, program.getTypeChecker(), changes, program.getCompilerOptions().target);
@@ -14,14 +13,13 @@ namespace ts.codefix {
}
});
// No support for fix-all since this applies to the whole file at once anyway.
return [{ description, changes, fixId: undefined }];
return [createCodeFixActionNoFixId(changes, Diagnostics.Convert_to_ES6_module)];
},
});
function fixImportOfModuleExports(importingFile: SourceFile, exportingFile: SourceFile, changes: textChanges.ChangeTracker) {
for (const moduleSpecifier of importingFile.imports) {
const { text } = moduleSpecifier;
const imported = getResolvedModule(importingFile, text);
const imported = getResolvedModule(importingFile, moduleSpecifier.text);
if (!imported || imported.resolvedFileName !== exportingFile.fileName) {
continue;
}
@@ -29,7 +27,7 @@ namespace ts.codefix {
const importNode = importFromModuleSpecifier(moduleSpecifier);
switch (importNode.kind) {
case SyntaxKind.ImportEqualsDeclaration:
changes.replaceNode(importingFile, importNode, makeImport(importNode.name, /*namedImports*/ undefined, text));
changes.replaceNode(importingFile, importNode, makeImport(importNode.name, /*namedImports*/ undefined, moduleSpecifier));
break;
case SyntaxKind.CallExpression:
if (isRequireCall(importNode, /*checkArgumentIsStringLiteralLike*/ false)) {
@@ -111,7 +109,7 @@ namespace ts.codefix {
case SyntaxKind.CallExpression: {
if (isRequireCall(expression, /*checkArgumentIsStringLiteralLike*/ true)) {
// For side-effecting require() call, just make a side-effecting import.
changes.replaceNode(sourceFile, statement, makeImport(/*name*/ undefined, /*namedImports*/ undefined, expression.arguments[0].text));
changes.replaceNode(sourceFile, statement, makeImport(/*name*/ undefined, /*namedImports*/ undefined, expression.arguments[0]));
}
return false;
}
@@ -139,11 +137,11 @@ namespace ts.codefix {
}
if (isRequireCall(initializer, /*checkArgumentIsStringLiteralLike*/ true)) {
foundImport = true;
return convertSingleImport(sourceFile, name, initializer.arguments[0].text, changes, checker, identifiers, target);
return convertSingleImport(sourceFile, name, initializer.arguments[0], changes, checker, identifiers, target);
}
else if (isPropertyAccessExpression(initializer) && isRequireCall(initializer.expression, /*checkArgumentIsStringLiteralLike*/ true)) {
foundImport = true;
return convertPropertyAccessImport(name, initializer.name.text, initializer.expression.arguments[0].text, identifiers);
return convertPropertyAccessImport(name, initializer.name.text, initializer.expression.arguments[0], identifiers);
}
else {
// Move it out to its own variable statement.
@@ -157,7 +155,7 @@ namespace ts.codefix {
}
/** Converts `const name = require("moduleSpecifier").propertyName` */
function convertPropertyAccessImport(name: BindingName, propertyName: string, moduleSpecifier: string, identifiers: Identifiers): ReadonlyArray<Node> {
function convertPropertyAccessImport(name: BindingName, propertyName: string, moduleSpecifier: StringLiteralLike, identifiers: Identifiers): ReadonlyArray<Node> {
switch (name.kind) {
case SyntaxKind.ObjectBindingPattern:
case SyntaxKind.ArrayBindingPattern: {
@@ -257,7 +255,7 @@ namespace ts.codefix {
changes.replaceNodeWithNodes(sourceFile, statement, newNodes);
}
else {
changes.replaceNode(sourceFile, statement, convertExportsDotXEquals(text, right), { useNonAdjustedEndPosition: true });
changes.replaceNode(sourceFile, statement, convertExportsDotXEquals(text, right));
}
}
@@ -340,7 +338,7 @@ namespace ts.codefix {
function convertSingleImport(
file: SourceFile,
name: BindingName,
moduleSpecifier: string,
moduleSpecifier: StringLiteralLike,
changes: textChanges.ChangeTracker,
checker: TypeChecker,
identifiers: Identifiers,
@@ -362,7 +360,7 @@ namespace ts.codefix {
import x from "x";
const [a, b, c] = x;
*/
const tmp = makeUniqueName(moduleSpecifierToValidIdentifier(moduleSpecifier, target), identifiers);
const tmp = makeUniqueName(moduleSpecifierToValidIdentifier(moduleSpecifier.text, target), identifiers);
return [
makeImport(createIdentifier(tmp), /*namedImports*/ undefined, moduleSpecifier),
makeConst(/*modifiers*/ undefined, getSynthesizedDeepClone(name), createIdentifier(tmp)),
@@ -379,7 +377,7 @@ namespace ts.codefix {
* Convert `import x = require("x").`
* Also converts uses like `x.y()` to `y()` and uses a named import.
*/
function convertSingleIdentifierImport(file: SourceFile, name: Identifier, moduleSpecifier: string, changes: textChanges.ChangeTracker, checker: TypeChecker, identifiers: Identifiers): ReadonlyArray<Node> {
function convertSingleIdentifierImport(file: SourceFile, name: Identifier, moduleSpecifier: StringLiteralLike, changes: textChanges.ChangeTracker, checker: TypeChecker, identifiers: Identifiers): ReadonlyArray<Node> {
const nameSymbol = checker.getSymbolAtLocation(name);
// Maps from module property name to name actually used. (The same if there isn't shadowing.)
const namedBindingsNames = createMap<string>();
@@ -486,14 +484,14 @@ namespace ts.codefix {
getSynthesizedDeepClones(cls.members));
}
function makeSingleImport(localName: string, propertyName: string, moduleSpecifier: string): ImportDeclaration {
function makeSingleImport(localName: string, propertyName: string, moduleSpecifier: StringLiteralLike): ImportDeclaration {
return propertyName === "default"
? makeImport(createIdentifier(localName), /*namedImports*/ undefined, moduleSpecifier)
: makeImport(/*name*/ undefined, [makeImportSpecifier(propertyName, localName)], moduleSpecifier);
}
function makeImport(name: Identifier | undefined, namedImports: ReadonlyArray<ImportSpecifier> | undefined, moduleSpecifier: string): ImportDeclaration {
return makeImportDeclaration(name, namedImports, createLiteral(moduleSpecifier));
function makeImport(name: Identifier | undefined, namedImports: ReadonlyArray<ImportSpecifier> | undefined, moduleSpecifier: StringLiteralLike): ImportDeclaration {
return makeImportDeclaration(name, namedImports, moduleSpecifier);
}
export function makeImportDeclaration(name: Identifier, namedImports: ReadonlyArray<ImportSpecifier> | undefined, moduleSpecifier: Expression) {
@@ -8,8 +8,8 @@ namespace ts.codefix {
const qualifiedName = getQualifiedName(context.sourceFile, context.span.start);
if (!qualifiedName) return undefined;
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, qualifiedName));
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Rewrite_as_the_indexed_access_type_0), [`${qualifiedName.left.text}["${qualifiedName.right.text}"]`]);
return [{ description, changes, fixId }];
const newText = `${qualifiedName.left.text}["${qualifiedName.right.text}"]`;
return [createCodeFixAction(changes, [Diagnostics.Rewrite_as_the_indexed_access_type_0, newText], fixId, Diagnostics.Rewrite_all_as_indexed_access_types)];
},
fixIds: [fixId],
getAllCodeActions: (context) => codeFixAll(context, errorCodes, (changes, diag) => {
+10 -34
View File
@@ -16,23 +16,18 @@ namespace ts.codefix {
}
const fixes: CodeFixAction[] = [
{
description: getLocaleSpecificMessage(Diagnostics.Disable_checking_for_this_file),
changes: [createFileTextChanges(sourceFile.fileName, [
// fixId unnecessary because adding `// @ts-nocheck` even once will ignore every error in the file.
createCodeFixActionNoFixId(
[createFileTextChanges(sourceFile.fileName, [
createTextChange(sourceFile.checkJsDirective
? createTextSpanFromBounds(sourceFile.checkJsDirective.pos, sourceFile.checkJsDirective.end)
: createTextSpan(0, 0), `// @ts-nocheck${getNewLineOrDefaultFromHost(host, formatContext.options)}`),
])],
// fixId unnecessary because adding `// @ts-nocheck` even once will ignore every error in the file.
fixId: undefined,
}];
Diagnostics.Disable_checking_for_this_file),
];
if (isValidSuppressLocation(sourceFile, span.start)) {
fixes.unshift({
description: getLocaleSpecificMessage(Diagnostics.Ignore_this_error_message),
changes: textChanges.ChangeTracker.with(context, t => makeChange(t, sourceFile, span.start)),
fixId,
});
if (textChanges.isValidLocationToAddComment(sourceFile, span.start)) {
fixes.unshift(createCodeFixAction(textChanges.ChangeTracker.with(context, t => makeChange(t, sourceFile, span.start)), Diagnostics.Ignore_this_error_message, fixId, Diagnostics.Add_ts_ignore_to_all_error_messages));
}
return fixes;
@@ -41,37 +36,18 @@ namespace ts.codefix {
getAllCodeActions: context => {
const seenLines = createMap<true>();
return codeFixAll(context, errorCodes, (changes, diag) => {
if (isValidSuppressLocation(diag.file!, diag.start!)) {
if (textChanges.isValidLocationToAddComment(diag.file!, diag.start!)) {
makeChange(changes, diag.file!, diag.start!, seenLines);
}
});
},
});
function isValidSuppressLocation(sourceFile: SourceFile, position: number) {
return !isInComment(sourceFile, position) && !isInString(sourceFile, position) && !isInTemplateString(sourceFile, position);
}
function makeChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, seenLines?: Map<true>) {
const { line: lineNumber } = getLineAndCharacterOfPosition(sourceFile, position);
// Only need to add `// @ts-ignore` for a line once.
if (seenLines && !addToSeen(seenLines, lineNumber)) {
return;
if (!seenLines || addToSeen(seenLines, lineNumber)) {
changes.insertCommentBeforeLine(sourceFile, lineNumber, position, " @ts-ignore");
}
const lineStartPosition = getStartPositionOfLine(lineNumber, sourceFile);
const startPosition = getFirstNonSpaceCharacterPosition(sourceFile.text, lineStartPosition);
// First try to see if we can put the '// @ts-ignore' on the previous line.
// We need to make sure that we are not in the middle of a string literal or a comment.
// If so, we do not want to separate the node from its comment if we can.
// Otherwise, add an extra new line immediately before the error span.
const insertAtLineStart = isValidSuppressLocation(sourceFile, startPosition);
const token = getTouchingToken(sourceFile, insertAtLineStart ? startPosition : position, /*includeJsDocComment*/ false);
const clone = setStartsOnNewLine(getSynthesizedDeepClone(token), true);
addSyntheticLeadingComment(clone, SyntaxKind.SingleLineCommentTrivia, " @ts-ignore");
changes.replaceNode(sourceFile, token, clone, { preserveLeadingWhitespace: true, prefix: insertAtLineStart ? undefined : changes.newLineCharacter });
}
}
+17 -43
View File
@@ -46,7 +46,7 @@ namespace ts.codefix {
},
});
interface Info { token: Identifier; classDeclaration: ClassLikeDeclaration; makeStatic: boolean; classDeclarationSourceFile: SourceFile; inJs: boolean; call: CallExpression; }
interface Info { token: Identifier; classDeclaration: ClassLikeDeclaration; makeStatic: boolean; classDeclarationSourceFile: SourceFile; inJs: boolean; call: CallExpression | undefined; }
function getInfo(tokenSourceFile: SourceFile, tokenPos: number, checker: TypeChecker): Info | undefined {
// The identifier of the missing property. eg:
// this.missing = 1;
@@ -56,50 +56,26 @@ namespace ts.codefix {
return undefined;
}
const classAndMakeStatic = getClassAndMakeStatic(token, checker);
if (!classAndMakeStatic) {
return undefined;
}
const { classDeclaration, makeStatic } = classAndMakeStatic;
const { parent } = token;
if (!isPropertyAccessExpression(parent)) return undefined;
const leftExpressionType = skipConstraint(checker.getTypeAtLocation(parent.expression));
const { symbol } = leftExpressionType;
const classDeclaration = symbol && symbol.declarations && find(symbol.declarations, isClassLike);
if (!classDeclaration) return undefined;
const makeStatic = (leftExpressionType as TypeReference).target !== checker.getDeclaredTypeOfSymbol(symbol);
const classDeclarationSourceFile = classDeclaration.getSourceFile();
const inJs = isInJavaScriptFile(classDeclarationSourceFile);
const call = tryCast(token.parent.parent, isCallExpression);
const inJs = isSourceFileJavaScript(classDeclarationSourceFile);
const call = tryCast(parent.parent, isCallExpression);
return { token, classDeclaration, makeStatic, classDeclarationSourceFile, inJs, call };
}
function getClassAndMakeStatic(token: Node, checker: TypeChecker): { readonly classDeclaration: ClassLikeDeclaration, readonly makeStatic: boolean } | undefined {
const { parent } = token;
if (!isPropertyAccessExpression(parent)) {
return undefined;
}
if (parent.expression.kind === SyntaxKind.ThisKeyword) {
const containingClassMemberDeclaration = getThisContainer(token, /*includeArrowFunctions*/ false);
if (!isClassElement(containingClassMemberDeclaration)) {
return undefined;
}
const classDeclaration = containingClassMemberDeclaration.parent;
// Property accesses on `this` in a static method are accesses of a static member.
return isClassLike(classDeclaration) ? { classDeclaration, makeStatic: hasModifier(containingClassMemberDeclaration, ModifierFlags.Static) } : undefined;
}
else {
const leftExpressionType = checker.getTypeAtLocation(parent.expression);
const { symbol } = leftExpressionType;
if (!(symbol && leftExpressionType.flags & TypeFlags.Object && symbol.flags & SymbolFlags.Class)) {
return undefined;
}
const classDeclaration = cast(first(symbol.declarations), isClassLike);
// The expression is a class symbol but the type is not the instance-side.
return { classDeclaration, makeStatic: leftExpressionType !== checker.getDeclaredTypeOfSymbol(symbol) };
}
}
function getActionsForAddMissingMemberInJavaScriptFile(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): CodeFixAction | undefined {
const changes = textChanges.ChangeTracker.with(context, t => addMissingMemberInJs(t, classDeclarationSourceFile, classDeclaration, tokenName, makeStatic));
if (changes.length === 0) return undefined;
const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Initialize_static_property_0 : Diagnostics.Initialize_property_0_in_the_constructor), [tokenName]);
return { description, changes, fixId };
return changes.length === 0 ? undefined
: createCodeFixAction(changes, [makeStatic ? Diagnostics.Initialize_static_property_0 : Diagnostics.Initialize_property_0_in_the_constructor, tokenName], fixId, Diagnostics.Add_all_missing_members);
}
function addMissingMemberInJs(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): void {
@@ -143,9 +119,8 @@ namespace ts.codefix {
}
function createAddPropertyDeclarationAction(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, makeStatic: boolean, tokenName: string, typeNode: TypeNode): CodeFixAction {
const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Declare_static_property_0 : Diagnostics.Declare_property_0), [tokenName]);
const changes = textChanges.ChangeTracker.with(context, t => addPropertyDeclaration(t, classDeclarationSourceFile, classDeclaration, tokenName, typeNode, makeStatic));
return { description, changes, fixId };
return createCodeFixAction(changes, [makeStatic ? Diagnostics.Declare_static_property_0 : Diagnostics.Declare_property_0, tokenName], fixId, Diagnostics.Add_all_missing_members);
}
function addPropertyDeclaration(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, typeNode: TypeNode, makeStatic: boolean): void {
@@ -178,7 +153,7 @@ namespace ts.codefix {
const changes = textChanges.ChangeTracker.with(context, t => t.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, indexSignature));
// No fixId here because code-fix-all currently only works on adding individual named properties.
return { description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_property_0), [tokenName]), changes, fixId: undefined };
return createCodeFixActionNoFixId(changes, [Diagnostics.Add_index_signature_for_property_0, tokenName]);
}
function getActionForMethodDeclaration(
@@ -191,9 +166,8 @@ namespace ts.codefix {
inJs: boolean,
preferences: UserPreferences,
): CodeFixAction | undefined {
const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0), [token.text]);
const changes = textChanges.ChangeTracker.with(context, t => addMethodDeclaration(t, classDeclarationSourceFile, classDeclaration, token, callExpression, makeStatic, inJs, preferences));
return { description, changes, fixId };
return createCodeFixAction(changes, [makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0, token.text], fixId, Diagnostics.Add_all_missing_members);
}
function addMethodDeclaration(
@@ -12,7 +12,7 @@ namespace ts.codefix {
const nodes = getNodes(sourceFile, span.start);
if (!nodes) return undefined;
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, nodes));
return [{ description: getLocaleSpecificMessage(Diagnostics.Add_async_modifier_to_containing_function), changes, fixId }];
return [createCodeFixAction(changes, Diagnostics.Add_async_modifier_to_containing_function, fixId, Diagnostics.Add_all_missing_async_modifiers)];
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
+7 -18
View File
@@ -5,38 +5,27 @@ namespace ts.codefix {
registerCodeFix({
errorCodes,
getCodeActions: context => {
const codeAction = tryGetCodeActionForInstallPackageTypes(context.host, context.sourceFile.fileName, getModuleName(context.sourceFile, context.span.start));
return codeAction && [{ fixId, ...codeAction }];
const { host, sourceFile, span: { start } } = context;
const packageName = getTypesPackageNameToInstall(host, sourceFile, start);
return packageName === undefined ? []
: [createCodeFixAction(/*changes*/ [], [Diagnostics.Install_0, packageName], fixId, Diagnostics.Install_all_missing_types_packages, getCommand(sourceFile.fileName, packageName))];
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (_, diag, commands) => {
const pkg = getTypesPackageNameToInstall(context.host, getModuleName(diag.file, diag.start));
const pkg = getTypesPackageNameToInstall(context.host, diag.file, diag.start);
if (pkg) {
commands.push(getCommand(diag.file.fileName, pkg));
}
}),
});
function getModuleName(sourceFile: SourceFile, pos: number): string {
return cast(getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false), isStringLiteral).text;
}
function getCommand(fileName: string, packageName: string): InstallPackageAction {
return { type: "install package", file: fileName, packageName };
}
function getTypesPackageNameToInstall(host: LanguageServiceHost, moduleName: string): string | undefined {
function getTypesPackageNameToInstall(host: LanguageServiceHost, sourceFile: SourceFile, pos: number): string | undefined {
const moduleName = cast(getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false), isStringLiteral).text;
const { packageName } = getPackageName(moduleName);
// If !registry, registry not available yet, can't do anything.
return host.isKnownTypesPackageName(packageName) ? getTypesPackageName(packageName) : undefined;
}
function tryGetCodeActionForInstallPackageTypes(host: LanguageServiceHost, fileName: string, moduleName: string): CodeAction | undefined {
const packageName = getTypesPackageNameToInstall(host, moduleName);
return packageName === undefined ? undefined : {
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Install_0), [packageName]),
changes: [],
commands: [getCommand(fileName, packageName)],
};
}
}
@@ -11,7 +11,7 @@ namespace ts.codefix {
const { program, sourceFile, span } = context;
const changes = textChanges.ChangeTracker.with(context, t =>
addMissingMembers(getClass(sourceFile, span.start), sourceFile, program.getTypeChecker(), t, context.preferences));
return changes.length === 0 ? undefined : [{ description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class), changes, fixId }];
return changes.length === 0 ? undefined : [createCodeFixAction(changes, Diagnostics.Implement_inherited_abstract_class, fixId, Diagnostics.Implement_all_inherited_abstract_classes)];
},
fixIds: [fixId],
getAllCodeActions: context => {
@@ -11,9 +11,7 @@ namespace ts.codefix {
const checker = program.getTypeChecker();
return mapDefined<ExpressionWithTypeArguments, CodeFixAction>(getClassImplementsHeritageClauseElements(classDeclaration), implementedTypeNode => {
const changes = textChanges.ChangeTracker.with(context, t => addMissingDeclarations(checker, implementedTypeNode, sourceFile, classDeclaration, t, context.preferences));
if (changes.length === 0) return undefined;
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Implement_interface_0), [implementedTypeNode.getText()]);
return { description, changes, fixId };
return changes.length === 0 ? undefined : createCodeFixAction(changes, [Diagnostics.Implement_interface_0, implementedTypeNode.getText(sourceFile)], fixId, Diagnostics.Implement_all_unimplemented_interfaces);
});
},
fixIds: [fixId],
@@ -50,10 +48,10 @@ namespace ts.codefix {
const classType = checker.getTypeAtLocation(classDeclaration);
if (!checker.getIndexTypeOfType(classType, IndexKind.Number)) {
if (!classType.getNumberIndexType()) {
createMissingIndexSignatureDeclaration(implementedType, IndexKind.Number);
}
if (!checker.getIndexTypeOfType(classType, IndexKind.String)) {
if (!classType.getStringIndexType()) {
createMissingIndexSignatureDeclaration(implementedType, IndexKind.String);
}
@@ -10,7 +10,7 @@ namespace ts.codefix {
if (!nodes) return undefined;
const { constructor, superCall } = nodes;
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, constructor, superCall));
return [{ description: getLocaleSpecificMessage(Diagnostics.Make_super_call_the_first_statement_in_the_constructor), changes, fixId }];
return [createCodeFixAction(changes, Diagnostics.Make_super_call_the_first_statement_in_the_constructor, fixId, Diagnostics.Make_all_super_calls_the_first_statement_in_their_constructor)];
},
fixIds: [fixId],
getAllCodeActions(context) {
@@ -8,7 +8,7 @@ namespace ts.codefix {
const { sourceFile, span } = context;
const ctr = getNode(sourceFile, span.start);
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, ctr));
return [{ description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call), changes, fixId }];
return [createCodeFixAction(changes, Diagnostics.Add_missing_super_call, fixId, Diagnostics.Add_all_missing_super_calls)];
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) =>
@@ -10,7 +10,7 @@ namespace ts.codefix {
if (!nodes) return undefined;
const { extendsToken, heritageClauses } = nodes;
const changes = textChanges.ChangeTracker.with(context, t => doChanges(t, sourceFile, extendsToken, heritageClauses));
return [{ description: getLocaleSpecificMessage(Diagnostics.Change_extends_to_implements), changes, fixId }];
return [createCodeFixAction(changes, Diagnostics.Change_extends_to_implements, fixId, Diagnostics.Change_all_extended_interfaces_to_implements)];
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
@@ -27,7 +27,7 @@ namespace ts.codefix {
}
function doChanges(changes: textChanges.ChangeTracker, sourceFile: SourceFile, extendsToken: Node, heritageClauses: ReadonlyArray<HeritageClause>): void {
changes.replaceNode(sourceFile, extendsToken, createToken(SyntaxKind.ImplementsKeyword), textChanges.useNonAdjustedPositions);
changes.replaceNode(sourceFile, extendsToken, createToken(SyntaxKind.ImplementsKeyword));
// If there is already an implements clause, replace the implements keyword with a comma.
if (heritageClauses.length === 2 &&
@@ -11,7 +11,7 @@ namespace ts.codefix {
return undefined;
}
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, token));
return [{ description: getLocaleSpecificMessage(Diagnostics.Add_this_to_unresolved_variable), changes, fixId }];
return [createCodeFixAction(changes, Diagnostics.Add_this_to_unresolved_variable, fixId, Diagnostics.Add_this_to_all_unresolved_variables_matching_a_member_name)];
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
@@ -30,6 +30,6 @@ namespace ts.codefix {
}
// TODO (https://github.com/Microsoft/TypeScript/issues/21246): use shared helper
suppressLeadingAndTrailingTrivia(token);
changes.replaceNode(sourceFile, token, createPropertyAccess(createThis(), token), textChanges.useNonAdjustedPositions);
changes.replaceNode(sourceFile, token, createPropertyAccess(createThis(), token));
}
}
@@ -5,7 +5,7 @@ namespace ts.codefix {
getCodeActions: getActionsForInvalidImport
});
function getActionsForInvalidImport(context: CodeFixContext): CodeAction[] | undefined {
function getActionsForInvalidImport(context: CodeFixContext): CodeFixAction[] | undefined {
const sourceFile = context.sourceFile;
// This is the whole import statement, eg:
@@ -19,11 +19,11 @@ namespace ts.codefix {
return getCodeFixesForImportDeclaration(context, node);
}
function getCodeFixesForImportDeclaration(context: CodeFixContext, node: ImportDeclaration) {
function getCodeFixesForImportDeclaration(context: CodeFixContext, node: ImportDeclaration): CodeFixAction[] {
const sourceFile = getSourceFileOfNode(node);
const namespace = getNamespaceDeclarationNode(node) as NamespaceImport;
const opts = context.program.getCompilerOptions();
const variations: CodeAction[] = [];
const variations: CodeFixAction[] = [];
// import Bluebird from "bluebird";
variations.push(createAction(context, sourceFile, node, makeImportDeclaration(namespace.name, /*namedImports*/ undefined, node.moduleSpecifier)));
@@ -41,13 +41,9 @@ namespace ts.codefix {
return variations;
}
function createAction(context: CodeFixContext, sourceFile: SourceFile, node: Node, replacement: Node): CodeAction {
// TODO: GH#21246 Should be able to use `replaceNode`, but be sure to preserve comments (see `codeFixCalledES2015Import11.ts`)
const changes = textChanges.ChangeTracker.with(context, t => t.replaceRange(sourceFile, { pos: node.getStart(), end: node.end }, replacement));
return {
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Replace_import_with_0), [changes[0].textChanges[0].newText]),
changes,
};
function createAction(context: CodeFixContext, sourceFile: SourceFile, node: Node, replacement: Node): CodeFixAction {
const changes = textChanges.ChangeTracker.with(context, t => t.replaceNode(sourceFile, node, replacement));
return createCodeFixActionNoFixId(changes, [Diagnostics.Replace_import_with_0, changes[0].textChanges[0].newText]);
}
registerCodeFix({
@@ -58,7 +54,7 @@ namespace ts.codefix {
getCodeActions: getActionsForUsageOfInvalidImport
});
function getActionsForUsageOfInvalidImport(context: CodeFixContext): CodeAction[] | undefined {
function getActionsForUsageOfInvalidImport(context: CodeFixContext): CodeFixAction[] | undefined {
const sourceFile = context.sourceFile;
const targetKind = Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures.code === context.errorCode ? SyntaxKind.CallExpression : SyntaxKind.NewExpression;
const node = findAncestor(getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false), a => a.kind === targetKind && a.getStart() === context.span.start && a.getEnd() === (context.span.start + context.span.length)) as CallExpression | NewExpression;
@@ -70,15 +66,13 @@ namespace ts.codefix {
if (!(type.symbol && (type.symbol as TransientSymbol).originatingImport)) {
return [];
}
const fixes: CodeAction[] = [];
const fixes: CodeFixAction[] = [];
const relatedImport = (type.symbol as TransientSymbol).originatingImport;
if (!isImportCall(relatedImport)) {
addRange(fixes, getCodeFixesForImportDeclaration(context, relatedImport));
}
fixes.push({
description: getLocaleSpecificMessage(Diagnostics.Use_synthetic_default_member),
changes: textChanges.ChangeTracker.with(context, t => t.replaceNode(sourceFile, expr, createPropertyAccess(expr, "default"), {})),
});
const changes = textChanges.ChangeTracker.with(context, t => t.replaceNode(sourceFile, expr, createPropertyAccess(expr, "default"), {}));
fixes.push(createCodeFixActionNoFixId(changes, Diagnostics.Use_synthetic_default_member));
return fixes;
}
}
+4 -6
View File
@@ -12,19 +12,17 @@ namespace ts.codefix {
if (!info) return undefined;
const { typeNode, type } = info;
const original = typeNode.getText(sourceFile);
const actions = [fix(type, fixIdPlain)];
const actions = [fix(type, fixIdPlain, Diagnostics.Change_all_jsdoc_style_types_to_TypeScript)];
if (typeNode.kind === SyntaxKind.JSDocNullableType) {
// for nullable types, suggest the flow-compatible `T | null | undefined`
// in addition to the jsdoc/closure-compatible `T | null`
actions.push(fix(checker.getNullableType(type, TypeFlags.Undefined), fixIdNullable));
actions.push(fix(checker.getNullableType(type, TypeFlags.Undefined), fixIdNullable, Diagnostics.Change_all_jsdoc_style_types_to_TypeScript_and_add_undefined_to_nullable_types));
}
return actions;
function fix(type: Type, fixId: string): CodeFixAction {
const newText = checker.typeToString(type);
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_0_to_1), [original, newText]);
function fix(type: Type, fixId: string, fixAllDescription: DiagnosticMessage): CodeFixAction {
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, typeNode, type, checker));
return { description, changes, fixId };
return createCodeFixAction(changes, [Diagnostics.Change_0_to_1, original, checker.typeToString(type)], fixId, fixAllDescription);
}
},
fixIds: [fixIdPlain, fixIdNullable],
+1 -2
View File
@@ -13,8 +13,7 @@ namespace ts.codefix {
if (!info) return undefined;
const { node, suggestion } = info;
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, node, suggestion));
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_spelling_to_0), [suggestion]);
return [{ description, changes, fixId }];
return [createCodeFixAction(changes, [Diagnostics.Change_spelling_to_0, suggestion], fixId, Diagnostics.Fix_all_detected_spelling_errors)];
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
@@ -10,27 +10,24 @@ namespace ts.codefix {
const propertyDeclaration = getPropertyDeclaration(context.sourceFile, context.span.start);
if (!propertyDeclaration) return;
const newLineCharacter = getNewLineOrDefaultFromHost(context.host, context.formatContext.options);
const result = [
getActionForAddMissingUndefinedType(context, propertyDeclaration),
getActionForAddMissingDefiniteAssignmentAssertion(context, propertyDeclaration, newLineCharacter)
getActionForAddMissingDefiniteAssignmentAssertion(context, propertyDeclaration)
];
append(result, getActionForAddMissingInitializer(context, propertyDeclaration, newLineCharacter));
append(result, getActionForAddMissingInitializer(context, propertyDeclaration));
return result;
},
fixIds: [fixIdAddDefiniteAssignmentAssertions, fixIdAddUndefinedType, fixIdAddInitializer],
getAllCodeActions: context => {
const newLineCharacter = getNewLineOrDefaultFromHost(context.host, context.formatContext.options);
return codeFixAll(context, errorCodes, (changes, diag) => {
const propertyDeclaration = getPropertyDeclaration(diag.file, diag.start);
if (!propertyDeclaration) return;
switch (context.fixId) {
case fixIdAddDefiniteAssignmentAssertions:
addDefiniteAssignmentAssertion(changes, diag.file, propertyDeclaration, newLineCharacter);
addDefiniteAssignmentAssertion(changes, diag.file, propertyDeclaration);
break;
case fixIdAddUndefinedType:
addUndefinedType(changes, diag.file, propertyDeclaration);
@@ -40,7 +37,7 @@ namespace ts.codefix {
const initializer = getInitializer(checker, propertyDeclaration);
if (!initializer) return;
addInitializer(changes, diag.file, propertyDeclaration, initializer, newLineCharacter);
addInitializer(changes, diag.file, propertyDeclaration, initializer);
break;
default:
Debug.fail(JSON.stringify(context.fixId));
@@ -54,13 +51,12 @@ namespace ts.codefix {
return isIdentifier(token) ? cast(token.parent, isPropertyDeclaration) : undefined;
}
function getActionForAddMissingDefiniteAssignmentAssertion (context: CodeFixContext, propertyDeclaration: PropertyDeclaration, newLineCharacter: string): CodeFixAction {
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_definite_assignment_assertion_to_property_0), [propertyDeclaration.getText()]);
const changes = textChanges.ChangeTracker.with(context, t => addDefiniteAssignmentAssertion(t, context.sourceFile, propertyDeclaration, newLineCharacter));
return { description, changes, fixId: fixIdAddDefiniteAssignmentAssertions };
function getActionForAddMissingDefiniteAssignmentAssertion (context: CodeFixContext, propertyDeclaration: PropertyDeclaration): CodeFixAction {
const changes = textChanges.ChangeTracker.with(context, t => addDefiniteAssignmentAssertion(t, context.sourceFile, propertyDeclaration));
return createCodeFixAction(changes, [Diagnostics.Add_definite_assignment_assertion_to_property_0, propertyDeclaration.getText()], fixIdAddDefiniteAssignmentAssertions, Diagnostics.Add_definite_assignment_assertions_to_all_uninitialized_properties);
}
function addDefiniteAssignmentAssertion(changeTracker: textChanges.ChangeTracker, propertyDeclarationSourceFile: SourceFile, propertyDeclaration: PropertyDeclaration, newLineCharacter: string): void {
function addDefiniteAssignmentAssertion(changeTracker: textChanges.ChangeTracker, propertyDeclarationSourceFile: SourceFile, propertyDeclaration: PropertyDeclaration): void {
const property = updateProperty(
propertyDeclaration,
propertyDeclaration.decorators,
@@ -70,13 +66,12 @@ namespace ts.codefix {
propertyDeclaration.type,
propertyDeclaration.initializer
);
changeTracker.replaceNode(propertyDeclarationSourceFile, propertyDeclaration, property, { suffix: newLineCharacter });
changeTracker.replaceNode(propertyDeclarationSourceFile, propertyDeclaration, property);
}
function getActionForAddMissingUndefinedType (context: CodeFixContext, propertyDeclaration: PropertyDeclaration): CodeFixAction {
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_undefined_type_to_property_0), [propertyDeclaration.name.getText()]);
const changes = textChanges.ChangeTracker.with(context, t => addUndefinedType(t, context.sourceFile, propertyDeclaration));
return { description, changes, fixId: fixIdAddUndefinedType };
return createCodeFixAction(changes, [Diagnostics.Add_undefined_type_to_property_0, propertyDeclaration.name.getText()], fixIdAddUndefinedType, Diagnostics.Add_undefined_type_to_all_uninitialized_properties);
}
function addUndefinedType(changeTracker: textChanges.ChangeTracker, propertyDeclarationSourceFile: SourceFile, propertyDeclaration: PropertyDeclaration): void {
@@ -85,17 +80,16 @@ namespace ts.codefix {
changeTracker.replaceNode(propertyDeclarationSourceFile, propertyDeclaration.type, createUnionTypeNode(types));
}
function getActionForAddMissingInitializer (context: CodeFixContext, propertyDeclaration: PropertyDeclaration, newLineCharacter: string): CodeFixAction | undefined {
function getActionForAddMissingInitializer(context: CodeFixContext, propertyDeclaration: PropertyDeclaration): CodeFixAction | undefined {
const checker = context.program.getTypeChecker();
const initializer = getInitializer(checker, propertyDeclaration);
if (!initializer) return undefined;
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_initializer_to_property_0), [propertyDeclaration.name.getText()]);
const changes = textChanges.ChangeTracker.with(context, t => addInitializer(t, context.sourceFile, propertyDeclaration, initializer, newLineCharacter));
return { description, changes, fixId: fixIdAddInitializer };
const changes = textChanges.ChangeTracker.with(context, t => addInitializer(t, context.sourceFile, propertyDeclaration, initializer));
return createCodeFixAction(changes, [Diagnostics.Add_initializer_to_property_0, propertyDeclaration.name.getText()], fixIdAddInitializer, Diagnostics.Add_initializers_to_all_uninitialized_properties);
}
function addInitializer (changeTracker: textChanges.ChangeTracker, propertyDeclarationSourceFile: SourceFile, propertyDeclaration: PropertyDeclaration, initializer: Expression, newLineCharacter: string): void {
function addInitializer (changeTracker: textChanges.ChangeTracker, propertyDeclarationSourceFile: SourceFile, propertyDeclaration: PropertyDeclaration, initializer: Expression): void {
const property = updateProperty(
propertyDeclaration,
propertyDeclaration.decorators,
@@ -105,7 +99,7 @@ namespace ts.codefix {
propertyDeclaration.type,
initializer
);
changeTracker.replaceNode(propertyDeclarationSourceFile, propertyDeclaration, property, { suffix: newLineCharacter });
changeTracker.replaceNode(propertyDeclarationSourceFile, propertyDeclaration, property);
}
function getInitializer(checker: TypeChecker, propertyDeclaration: PropertyDeclaration): Expression | undefined {
@@ -13,9 +13,8 @@ namespace ts.codefix {
const { errorCode, sourceFile } = context;
const importDecl = tryGetFullImport(sourceFile, context.span.start);
if (importDecl) {
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Remove_import_from_0), [showModuleSpecifier(importDecl)]);
const changes = textChanges.ChangeTracker.with(context, t => t.deleteNode(sourceFile, importDecl));
return [{ description, changes, fixId: fixIdDelete }];
return [createCodeFixAction(changes, [Diagnostics.Remove_import_from_0, showModuleSpecifier(importDecl)], fixIdDelete, Diagnostics.Delete_all_unused_declarations)];
}
const token = getToken(sourceFile, textSpanEnd(context.span));
@@ -23,14 +22,12 @@ namespace ts.codefix {
const deletion = textChanges.ChangeTracker.with(context, t => tryDeleteDeclaration(t, sourceFile, token));
if (deletion.length) {
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Remove_declaration_for_Colon_0), [token.getText()]);
result.push({ description, changes: deletion, fixId: fixIdDelete });
result.push(createCodeFixAction(deletion, [Diagnostics.Remove_declaration_for_Colon_0, token.getText(sourceFile)], fixIdDelete, Diagnostics.Delete_all_unused_declarations));
}
const prefix = textChanges.ChangeTracker.with(context, t => tryPrefixDeclaration(t, errorCode, sourceFile, token));
if (prefix.length) {
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Prefix_0_with_an_underscore), [token.getText()]);
result.push({ description, changes: prefix, fixId: fixIdPrefix });
result.push(createCodeFixAction(prefix, [Diagnostics.Prefix_0_with_an_underscore, token.getText(sourceFile)], fixIdPrefix, Diagnostics.Prefix_all_unused_declarations_with_where_possible));
}
return result;
@@ -165,7 +162,7 @@ namespace ts.codefix {
// and trailing trivia will remain.
suppressLeadingAndTrailingTrivia(newFunction);
changes.replaceNode(sourceFile, oldFunction, newFunction, textChanges.useNonAdjustedPositions);
changes.replaceNode(sourceFile, oldFunction, newFunction);
}
else {
changes.deleteNodeInList(sourceFile, parent);
+5 -6
View File
@@ -33,10 +33,9 @@ namespace ts.codefix {
preferences: UserPreferences;
}
function createCodeAction(descriptionDiagnostic: DiagnosticMessage, diagnosticArgs: string[], changes: FileTextChanges[]): CodeFixAction {
const description = formatMessage.apply(undefined, [undefined, descriptionDiagnostic].concat(<any[]>diagnosticArgs));
function createCodeAction(descriptionDiagnostic: DiagnosticMessage, diagnosticArgs: [string, string], changes: FileTextChanges[]): CodeFixAction {
// TODO: GH#20315
return { description, changes, fixId: undefined };
return createCodeFixActionNoFixId(changes, [descriptionDiagnostic, ...diagnosticArgs] as [DiagnosticMessage, string, string]);
}
function convertToImportCodeFixContext(context: CodeFixContext, symbolToken: Node, symbolName: string): ImportCodeFixContext {
@@ -640,13 +639,13 @@ namespace ts.codefix {
return createCodeAction(Diagnostics.Change_0_to_1, [symbolName, `${namespacePrefix}.${symbolName}`], changes);
}
function getImportCodeActions(context: CodeFixContext): CodeAction[] {
function getImportCodeActions(context: CodeFixContext): CodeFixAction[] {
return context.errorCode === Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code
? getActionsForUMDImport(context)
: getActionsForNonUMDImport(context);
}
function getActionsForUMDImport(context: CodeFixContext): CodeAction[] {
function getActionsForUMDImport(context: CodeFixContext): CodeFixAction[] {
const token = getTokenAtPosition(context.sourceFile, context.span.start, /*includeJsDocComment*/ false);
const checker = context.program.getTypeChecker();
@@ -702,7 +701,7 @@ namespace ts.codefix {
}
}
function getActionsForNonUMDImport(context: CodeFixContext): CodeAction[] | undefined {
function getActionsForNonUMDImport(context: CodeFixContext): CodeFixAction[] | undefined {
// This will always be an Identifier, since the diagnostics we fix only fail on identifiers.
const { sourceFile, span, program, cancellationToken } = context;
const checker = program.getTypeChecker();
+3 -19
View File
@@ -33,10 +33,8 @@ namespace ts.codefix {
const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false);
let declaration!: Declaration;
const changes = textChanges.ChangeTracker.with(context, changes => { declaration = doChange(changes, sourceFile, token, errorCode, program, cancellationToken); });
if (changes.length === 0) return undefined;
const name = getNameOfDeclaration(declaration).getText();
const description = formatStringFromArgs(getLocaleSpecificMessage(getDiagnostic(errorCode, token)), [name]);
return [{ description, changes, fixId }];
return changes.length === 0 ? undefined
: [createCodeFixAction(changes, [getDiagnostic(errorCode, token), getNameOfDeclaration(declaration).getText(sourceFile)], fixId, Diagnostics.Infer_all_types_from_usage)];
},
fixIds: [fixId],
getAllCodeActions(context) {
@@ -60,7 +58,7 @@ namespace ts.codefix {
}
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node, errorCode: number, program: Program, cancellationToken: CancellationToken, seenFunctions?: Map<true>): Declaration | undefined {
if (!isAllowedTokenKind(token.kind)) {
if (!isParameterPropertyModifier(token.kind) && token.kind !== SyntaxKind.Identifier && token.kind !== SyntaxKind.DotDotDotToken) {
return undefined;
}
@@ -127,20 +125,6 @@ namespace ts.codefix {
}
}
function isAllowedTokenKind(kind: SyntaxKind): boolean {
switch (kind) {
case SyntaxKind.Identifier:
case SyntaxKind.DotDotDotToken:
case SyntaxKind.PublicKeyword:
case SyntaxKind.PrivateKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.ReadonlyKeyword:
return true;
default:
return false;
}
}
function annotateVariableDeclaration(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: VariableDeclaration | PropertyDeclaration | PropertySignature, program: Program, cancellationToken: CancellationToken): void {
if (isIdentifier(declaration.name)) {
annotate(changes, sourceFile, declaration, inferTypeForVariableFromUsage(declaration.name, program, cancellationToken), program);
+2 -3
View File
@@ -8,9 +8,8 @@ namespace ts.codefix {
const { sourceFile, span: { start } } = context;
const info = getInfo(sourceFile, start);
if (!info) return undefined;
const description = getLocaleSpecificMessage(Diagnostics.Convert_to_default_import);
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, info));
return [{ description, changes, fixId }];
return [createCodeFixAction(changes, Diagnostics.Convert_to_default_import, fixId, Diagnostics.Convert_all_to_default_imports)];
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
@@ -38,6 +37,6 @@ namespace ts.codefix {
}
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, info: Info): void {
changes.replaceNode(sourceFile, info.importNode, makeImportDeclaration(info.name, /*namedImports*/ undefined, info.moduleSpecifier), textChanges.useNonAdjustedPositions);
changes.replaceNode(sourceFile, info.importNode, makeImportDeclaration(info.name, /*namedImports*/ undefined, info.moduleSpecifier));
}
}
+124 -163
View File
@@ -86,11 +86,11 @@ namespace ts.Completions {
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: true, entries };
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.variableElement, sortText: "0" }));
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: true, entries };
const entries = completion.types.map(type => ({ name: type.value, kindModifiers: ScriptElementKindModifier.none, kind: ScriptElementKind.typeElement, sortText: "0" }));
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries };
}
default:
return Debug.assertNever(completion);
@@ -360,9 +360,14 @@ namespace ts.Completions {
}
const enum StringLiteralCompletionKind { Paths, Properties, Types }
interface StringLiteralCompletionsFromProperties {
readonly kind: StringLiteralCompletionKind.Properties;
readonly symbols: ReadonlyArray<Symbol>;
readonly hasIndexSignature: boolean;
}
type StringLiteralCompletion =
| { readonly kind: StringLiteralCompletionKind.Paths, readonly paths: ReadonlyArray<PathCompletions.PathCompletion> }
| { readonly kind: StringLiteralCompletionKind.Properties, readonly symbols: ReadonlyArray<Symbol> }
| StringLiteralCompletionsFromProperties
| { readonly kind: StringLiteralCompletionKind.Types, readonly types: ReadonlyArray<StringLiteralType> };
function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringLiteralLike, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost): StringLiteralCompletion | undefined {
switch (node.parent.kind) {
@@ -377,7 +382,7 @@ namespace ts.Completions {
// bar: string;
// }
// let x: Foo["/*completion position*/"]
return { kind: StringLiteralCompletionKind.Properties, symbols: typeChecker.getTypeFromTypeNode((node.parent.parent as IndexedAccessTypeNode).objectType).getApparentProperties() };
return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode((node.parent.parent as IndexedAccessTypeNode).objectType));
default:
return undefined;
}
@@ -396,8 +401,7 @@ namespace ts.Completions {
// foo({
// '/*completion position*/'
// });
const type = typeChecker.getContextualType(node.parent.parent);
return { kind: StringLiteralCompletionKind.Properties, symbols: type && type.getApparentProperties() };
return stringLiteralCompletionsFromProperties(typeChecker.getContextualType(node.parent.parent));
}
return fromContextualType();
@@ -410,7 +414,7 @@ namespace ts.Completions {
// }
// let a: A;
// a['/*completion position*/']
return { kind: StringLiteralCompletionKind.Properties, symbols: typeChecker.getTypeAtLocation(expression).getApparentProperties() };
return stringLiteralCompletionsFromProperties(typeChecker.getTypeAtLocation(expression));
}
return undefined;
}
@@ -454,13 +458,16 @@ namespace ts.Completions {
}
}
function getStringLiteralTypes(type: Type, typeChecker: TypeChecker, uniques = createMap<true>()): ReadonlyArray<StringLiteralType> {
if (type && type.flags & TypeFlags.TypeParameter) {
type = type.getConstraint();
}
return type && type.flags & TypeFlags.Union
function stringLiteralCompletionsFromProperties(type: Type | undefined): StringLiteralCompletionsFromProperties | undefined {
return type && { kind: StringLiteralCompletionKind.Properties, symbols: type.getApparentProperties(), hasIndexSignature: hasIndexSignature(type) };
}
function getStringLiteralTypes(type: Type | undefined, typeChecker: TypeChecker, uniques = createMap<true>()): ReadonlyArray<StringLiteralType> | undefined {
if (!type) return emptyArray;
type = skipConstraint(type);
return type.flags & TypeFlags.Union
? flatMap((<UnionType>type).types, t => getStringLiteralTypes(t, typeChecker, uniques))
: type && type.flags & TypeFlags.StringLiteral && !(type.flags & TypeFlags.EnumLiteral) && addToSeen(uniques, (type as StringLiteralType).value)
: type.flags & TypeFlags.StringLiteral && !(type.flags & TypeFlags.EnumLiteral) && addToSeen(uniques, (type as StringLiteralType).value)
? [type as StringLiteralType]
: emptyArray;
}
@@ -531,6 +538,15 @@ namespace ts.Completions {
): CompletionEntryDetails {
const typeChecker = program.getTypeChecker();
const { name } = entryId;
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);
}
// Compute all the completion symbols again.
const symbolCompletion = getSymbolCompletionFromEntryId(typeChecker, log, compilerOptions, sourceFile, position, entryId, allSourceFiles);
switch (symbolCompletion.type) {
@@ -550,29 +566,40 @@ namespace ts.Completions {
case "symbol": {
const { symbol, location, symbolToOriginInfoMap, previousToken } = symbolCompletion;
const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(symbolToOriginInfoMap, symbol, program, typeChecker, host, compilerOptions, sourceFile, previousToken, formatContext, getCanonicalFileName, allSourceFiles, preferences);
const kindModifiers = SymbolDisplay.getSymbolModifiers(symbol);
const { displayParts, documentation, symbolKind, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All);
return { name, kindModifiers, kind: symbolKind, displayParts, documentation, tags, codeActions, source: sourceDisplay };
return createCompletionDetailsForSymbol(symbol, typeChecker, sourceFile, location, codeActions, sourceDisplay);
}
case "none": {
case "none":
// Didn't find a symbol with this name. See if we can find a keyword instead.
if (allKeywordsCompletions().some(c => c.name === name)) {
return {
name,
kind: ScriptElementKind.keyword,
kindModifiers: ScriptElementKindModifier.none,
displayParts: [displayPart(name, SymbolDisplayPartKind.keyword)],
documentation: undefined,
tags: undefined,
codeActions: undefined,
source: undefined,
};
}
return undefined;
}
return allKeywordsCompletions().some(c => c.name === name) ? createCompletionDetails(name, ScriptElementKindModifier.none, ScriptElementKind.keyword, [displayPart(name, SymbolDisplayPartKind.keyword)]) : undefined;
}
}
function createCompletionDetailsForSymbol(symbol: Symbol, checker: TypeChecker, sourceFile: SourceFile, location: Node, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[]): CompletionEntryDetails {
const { displayParts, documentation, symbolKind, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, sourceFile, location, location, SemanticMeaning.All);
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): 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);
}
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 {
return { name, kindModifiers, kind, displayParts, documentation, tags, codeActions, source };
}
interface CodeActionsAndSourceDisplay {
readonly codeActions: CodeAction[] | undefined;
readonly sourceDisplay: SymbolDisplayPart[] | undefined;
@@ -1031,6 +1058,8 @@ namespace ts.Completions {
}
function addTypeProperties(type: Type): void {
isNewIdentifierLocation = hasIndexSignature(type);
if (isSourceFileJavaScript(sourceFile)) {
// In javascript files, for union types, we don't just get the members that
// the individual types have in common, we also include all the members that
@@ -1380,10 +1409,10 @@ namespace ts.Completions {
}
// Previous token may have been a keyword that was converted to an identifier.
switch (previousToken.getText()) {
case "public":
case "protected":
case "private":
switch (keywordForNode(previousToken)) {
case SyntaxKind.PublicKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.PrivateKeyword:
return true;
}
}
@@ -1432,11 +1461,9 @@ namespace ts.Completions {
let existingMembers: ReadonlyArray<Declaration>;
if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) {
// We are completing on contextual types, but may also include properties
// other than those within the declared type.
isNewIdentifierLocation = true;
const typeForObject = typeChecker.getContextualType(objectLikeContainer);
if (!typeForObject) return GlobalsSearch.Fail;
isNewIdentifierLocation = hasIndexSignature(typeForObject);
typeMembers = getPropertiesForCompletion(typeForObject, typeChecker, /*isForAccess*/ false);
existingMembers = objectLikeContainer.properties;
}
@@ -1535,43 +1562,34 @@ namespace ts.Completions {
completionKind = CompletionKind.MemberLike;
// Declaring new property/method/accessor
isNewIdentifierLocation = true;
// Has keywords for class elements
keywordFilters = isClassLike(decl) ? KeywordCompletionFilters.ClassElementKeywords : KeywordCompletionFilters.InterfaceElementKeywords;
// If you're in an interface you don't want to repeat things from super-interface. So just stop here.
if (!isClassLike(decl)) return GlobalsSearch.Success;
const baseTypeNode = getClassExtendsHeritageClauseElement(decl);
const implementsTypeNodes = getClassImplementsHeritageClauseElements(decl);
if (!baseTypeNode && !implementsTypeNodes) return GlobalsSearch.Success;
const classElement = contextToken.parent;
const classElementModifierFlags = (isClassElement(classElement) ? getModifierFlags(classElement) : ModifierFlags.None)
// If this is context token is not something we are editing now, consider if this would lead to be modifier
| (isIdentifier(contextToken) && !isCurrentlyEditingNode(contextToken) ? modifierToFlag(contextToken.originalKeywordKind) : ModifierFlags.None);
// No member list for private methods
if (classElementModifierFlags & ModifierFlags.Private) return GlobalsSearch.Success;
let baseClassTypeToGetPropertiesFrom: Type | undefined;
if (baseTypeNode) {
baseClassTypeToGetPropertiesFrom = typeChecker.getTypeAtLocation(baseTypeNode);
if (classElementModifierFlags & ModifierFlags.Static) {
// Use static class to get property symbols from
baseClassTypeToGetPropertiesFrom = typeChecker.getTypeOfSymbolAtLocation(baseClassTypeToGetPropertiesFrom.symbol, decl);
let classElementModifierFlags = isClassElement(classElement) && getModifierFlags(classElement);
// If this is context token is not something we are editing now, consider if this would lead to be modifier
if (contextToken.kind === SyntaxKind.Identifier && !isCurrentlyEditingNode(contextToken)) {
switch (contextToken.getText()) {
case "private":
classElementModifierFlags = classElementModifierFlags | ModifierFlags.Private;
break;
case "static":
classElementModifierFlags = classElementModifierFlags | ModifierFlags.Static;
break;
}
}
const implementedInterfaceTypePropertySymbols = !implementsTypeNodes || (classElementModifierFlags & ModifierFlags.Static)
? emptyArray
: flatMap(implementsTypeNodes, typeNode => typeChecker.getPropertiesOfType(typeChecker.getTypeAtLocation(typeNode)));
// List of property symbols of base type that are not private and already implemented
symbols = filterClassMembersList(
baseClassTypeToGetPropertiesFrom ? typeChecker.getPropertiesOfType(baseClassTypeToGetPropertiesFrom) : emptyArray,
implementedInterfaceTypePropertySymbols,
decl.members,
classElementModifierFlags);
// No member list for private methods
if (!(classElementModifierFlags & ModifierFlags.Private)) {
// List of property symbols of base type that are not private and already implemented
const baseSymbols = flatMap(getAllSuperTypeNodes(decl), baseTypeNode => {
const type = typeChecker.getTypeAtLocation(baseTypeNode);
return typeChecker.getPropertiesOfType(classElementModifierFlags & ModifierFlags.Static ? typeChecker.getTypeOfSymbolAtLocation(type.symbol, decl) : type);
});
symbols = filterClassMembersList(baseSymbols, decl.members, classElementModifierFlags);
}
return GlobalsSearch.Success;
}
@@ -1616,14 +1634,9 @@ namespace ts.Completions {
return undefined;
}
function isParameterOfConstructorDeclaration(node: Node) {
return isParameter(node) && isConstructorDeclaration(node.parent);
}
function isConstructorParameterCompletion(node: Node) {
return node.parent &&
isParameterOfConstructorDeclaration(node.parent) &&
(isConstructorParameterCompletionKeyword(node.kind) || isDeclarationName(node));
function isConstructorParameterCompletion(node: Node): boolean {
return !!node.parent && isParameter(node.parent) && isConstructorDeclaration(node.parent.parent)
&& (isParameterPropertyModifier(node.kind) || isDeclarationName(node));
}
/**
@@ -1808,8 +1821,7 @@ namespace ts.Completions {
// If the previous token is keyword correspoding to class member completion keyword
// there will be completion available here
if (isClassMemberCompletionKeywordText(contextToken.getText()) &&
isFromObjectTypeDeclaration(contextToken)) {
if (isClassMemberCompletionKeyword(keywordForNode(contextToken)) && isFromObjectTypeDeclaration(contextToken)) {
return false;
}
@@ -1819,29 +1831,29 @@ namespace ts.Completions {
// - its name of the parameter and not being edited
// eg. constructor(a |<- this shouldnt show completion
if (!isIdentifier(contextToken) ||
isConstructorParameterCompletionKeywordText(contextToken.getText()) ||
isParameterPropertyModifier(keywordForNode(contextToken)) ||
isCurrentlyEditingNode(contextToken)) {
return false;
}
}
// Previous token may have been a keyword that was converted to an identifier.
switch (contextToken.getText()) {
case "abstract":
case "async":
case "class":
case "const":
case "declare":
case "enum":
case "function":
case "interface":
case "let":
case "private":
case "protected":
case "public":
case "static":
case "var":
case "yield":
switch (keywordForNode(contextToken)) {
case SyntaxKind.AbstractKeyword:
case SyntaxKind.AsyncKeyword:
case SyntaxKind.ClassKeyword:
case SyntaxKind.ConstKeyword:
case SyntaxKind.DeclareKeyword:
case SyntaxKind.EnumKeyword:
case SyntaxKind.FunctionKeyword:
case SyntaxKind.InterfaceKeyword:
case SyntaxKind.LetKeyword:
case SyntaxKind.PrivateKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.PublicKeyword:
case SyntaxKind.StaticKeyword:
case SyntaxKind.VarKeyword:
case SyntaxKind.YieldKeyword:
return true;
}
@@ -1945,12 +1957,8 @@ namespace ts.Completions {
*
* @returns Symbols to be suggested in an class element depending on existing memebers and symbol flags
*/
function filterClassMembersList(
baseSymbols: ReadonlyArray<Symbol>,
implementingTypeSymbols: ReadonlyArray<Symbol>,
existingMembers: ReadonlyArray<ClassElement>,
currentClassElementModifierFlags: ModifierFlags): Symbol[] {
const existingMemberNames = createUnderscoreEscapedMap<boolean>();
function filterClassMembersList(baseSymbols: ReadonlyArray<Symbol>, existingMembers: ReadonlyArray<ClassElement>, currentClassElementModifierFlags: ModifierFlags): Symbol[] {
const existingMemberNames = createUnderscoreEscapedMap<true>();
for (const m of existingMembers) {
// Ignore omitted expressions for missing members
if (m.kind !== SyntaxKind.PropertyDeclaration &&
@@ -1971,10 +1979,7 @@ namespace ts.Completions {
}
// do not filter it out if the static presence doesnt match
const mIsStatic = hasModifier(m, ModifierFlags.Static);
const currentElementIsStatic = !!(currentClassElementModifierFlags & ModifierFlags.Static);
if ((mIsStatic && !currentElementIsStatic) ||
(!mIsStatic && currentElementIsStatic)) {
if (hasModifier(m, ModifierFlags.Static) !== !!(currentClassElementModifierFlags & ModifierFlags.Static)) {
continue;
}
@@ -1984,24 +1989,10 @@ namespace ts.Completions {
}
}
const result: Symbol[] = [];
addPropertySymbols(baseSymbols, ModifierFlags.Private);
addPropertySymbols(implementingTypeSymbols, ModifierFlags.NonPublicAccessibilityModifier);
return result;
function addPropertySymbols(properties: ReadonlyArray<Symbol>, inValidModifierFlags: ModifierFlags) {
for (const property of properties) {
if (isValidProperty(property, inValidModifierFlags)) {
result.push(property);
}
}
}
function isValidProperty(propertySymbol: Symbol, inValidModifierFlags: ModifierFlags) {
return !existingMemberNames.get(propertySymbol.escapedName) &&
propertySymbol.getDeclarations() &&
!(getDeclarationModifierFlagsFromSymbol(propertySymbol) & inValidModifierFlags);
}
return baseSymbols.filter(propertySymbol =>
!existingMemberNames.has(propertySymbol.escapedName) &&
!!propertySymbol.declarations &&
!(getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.Private));
}
/**
@@ -2027,7 +2018,7 @@ namespace ts.Completions {
}
function isCurrentlyEditingNode(node: Node): boolean {
return node.getStart() <= position && position <= node.getEnd();
return node.getStart(sourceFile) <= position && position <= node.getEnd();
}
}
@@ -2097,9 +2088,9 @@ namespace ts.Completions {
case KeywordCompletionFilters.InterfaceElementKeywords:
return isInterfaceOrTypeLiteralCompletionKeyword(kind);
case KeywordCompletionFilters.ConstructorParameterKeywords:
return isConstructorParameterCompletionKeyword(kind);
return isParameterPropertyModifier(kind);
case KeywordCompletionFilters.FunctionLikeBodyKeywords:
return isFunctionLikeBodyCompletionKeyword(kind);
return !isClassMemberCompletionKeyword(kind);
case KeywordCompletionFilters.TypeKeywords:
return isTypeKeyword(kind);
default:
@@ -2114,53 +2105,19 @@ namespace ts.Completions {
function isClassMemberCompletionKeyword(kind: SyntaxKind) {
switch (kind) {
case SyntaxKind.PublicKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.PrivateKeyword:
case SyntaxKind.AbstractKeyword:
case SyntaxKind.StaticKeyword:
case SyntaxKind.ConstructorKeyword:
case SyntaxKind.ReadonlyKeyword:
case SyntaxKind.GetKeyword:
case SyntaxKind.SetKeyword:
case SyntaxKind.AsyncKeyword:
return true;
default:
return isClassMemberModifier(kind);
}
}
function isClassMemberCompletionKeywordText(text: string) {
return isClassMemberCompletionKeyword(stringToToken(text));
}
function isConstructorParameterCompletionKeyword(kind: SyntaxKind) {
switch (kind) {
case SyntaxKind.PublicKeyword:
case SyntaxKind.PrivateKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.ReadonlyKeyword:
return true;
}
}
function isConstructorParameterCompletionKeywordText(text: string) {
return isConstructorParameterCompletionKeyword(stringToToken(text));
}
function isFunctionLikeBodyCompletionKeyword(kind: SyntaxKind) {
switch (kind) {
case SyntaxKind.PublicKeyword:
case SyntaxKind.PrivateKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.ReadonlyKeyword:
case SyntaxKind.ConstructorKeyword:
case SyntaxKind.StaticKeyword:
case SyntaxKind.AbstractKeyword:
case SyntaxKind.GetKeyword:
case SyntaxKind.SetKeyword:
case SyntaxKind.UndefinedKeyword:
return false;
}
return true;
function keywordForNode(node: Node): SyntaxKind {
return isIdentifier(node) ? node.originalKeywordKind || SyntaxKind.Unknown : node.kind;
}
function isEqualityOperatorKind(kind: SyntaxKind): kind is EqualityOperator {
@@ -2260,4 +2217,8 @@ namespace ts.Completions {
function isFromObjectTypeDeclaration(node: Node): boolean {
return node.parent && (isClassElement(node.parent) || isTypeElement(node.parent)) && isObjectTypeDeclaration(node.parent.parent);
}
function hasIndexSignature(type: Type): boolean {
return !!type.getStringIndexType() || !!type.getNumberIndexType();
}
}
+41 -96
View File
@@ -132,15 +132,8 @@ namespace ts.FindAllReferences {
const { node, name, kind, displayParts } = info;
const sourceFile = node.getSourceFile();
return {
containerKind: ScriptElementKind.unknown,
containerName: "",
fileName: sourceFile.fileName,
kind,
name,
textSpan: createTextSpanFromNode(node, sourceFile),
displayParts
};
const textSpan = getTextSpan(isComputedPropertyName(node) ? node.expression : node, sourceFile);
return { containerKind: ScriptElementKind.unknown, containerName: "", fileName: sourceFile.fileName, kind, name, textSpan, displayParts };
}
function getDefinitionKindAndDisplayParts(symbol: Symbol, checker: TypeChecker, node: Node): { displayParts: SymbolDisplayPart[], kind: ScriptElementKind } {
@@ -218,8 +211,8 @@ namespace ts.FindAllReferences {
return { fileName, span };
}
function getTextSpan(node: Node): TextSpan {
let start = node.getStart();
function getTextSpan(node: Node, sourceFile?: SourceFile): TextSpan {
let start = node.getStart(sourceFile);
let end = node.getEnd();
if (node.kind === SyntaxKind.StringLiteral) {
start += 1;
@@ -366,7 +359,7 @@ namespace ts.FindAllReferences.Core {
// otherwise we'll need to search globally (i.e. include each file).
const scope = getSymbolScope(symbol);
if (scope) {
getReferencesInContainer(scope, scope.getSourceFile(), search, state);
getReferencesInContainer(scope, scope.getSourceFile(), search, state, /*addReferencesHere*/ !(isSourceFile(scope) && !contains(sourceFiles, scope)));
}
else {
// Global search
@@ -595,9 +588,8 @@ namespace ts.FindAllReferences.Core {
function searchForImportedSymbol(symbol: Symbol, state: State): void {
for (const declaration of symbol.declarations) {
const exportingFile = declaration.getSourceFile();
if (state.includesSourceFile(exportingFile)) {
getReferencesInSourceFile(exportingFile, state.createSearch(declaration, symbol, ImportExport.Import), state);
}
// Need to search in the file even if it's not in the search-file set, because it might export the symbol.
getReferencesInSourceFile(exportingFile, state.createSearch(declaration, symbol, ImportExport.Import), state, state.includesSourceFile(exportingFile));
}
}
@@ -800,9 +792,9 @@ namespace ts.FindAllReferences.Core {
return references.length ? [{ definition: { type: "keyword", node: references[0].node }, references }] : undefined;
}
function getReferencesInSourceFile(sourceFile: SourceFile, search: Search, state: State): void {
function getReferencesInSourceFile(sourceFile: SourceFile, search: Search, state: State, addReferencesHere = true): void {
state.cancellationToken.throwIfCancellationRequested();
return getReferencesInContainer(sourceFile, sourceFile, search, state);
return getReferencesInContainer(sourceFile, sourceFile, search, state, addReferencesHere);
}
/**
@@ -810,17 +802,17 @@ namespace ts.FindAllReferences.Core {
* tuple of(searchSymbol, searchText, searchLocation, and searchMeaning).
* searchLocation: a node where the search value
*/
function getReferencesInContainer(container: Node, sourceFile: SourceFile, search: Search, state: State): void {
function getReferencesInContainer(container: Node, sourceFile: SourceFile, search: Search, state: State, addReferencesHere: boolean): void {
if (!state.markSearchedSymbol(sourceFile, search.symbol)) {
return;
}
for (const position of getPossibleSymbolReferencePositions(sourceFile, search.text, container)) {
getReferencesAtLocation(sourceFile, position, search, state);
getReferencesAtLocation(sourceFile, position, search, state, addReferencesHere);
}
}
function getReferencesAtLocation(sourceFile: SourceFile, position: number, search: Search, state: State): void {
function getReferencesAtLocation(sourceFile: SourceFile, position: number, search: Search, state: State, addReferencesHere: boolean): void {
const referenceLocation = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true);
if (!isValidReferencePosition(referenceLocation, search.text)) {
@@ -855,7 +847,7 @@ namespace ts.FindAllReferences.Core {
if (isExportSpecifier(parent)) {
Debug.assert(referenceLocation.kind === SyntaxKind.Identifier);
getReferencesAtExportSpecifier(referenceLocation as Identifier, referenceSymbol, parent, search, state);
getReferencesAtExportSpecifier(referenceLocation as Identifier, referenceSymbol, parent, search, state, addReferencesHere);
return;
}
@@ -867,7 +859,7 @@ namespace ts.FindAllReferences.Core {
switch (state.specialSearchKind) {
case SpecialSearchKind.None:
addReference(referenceLocation, relatedSymbol, state);
if (addReferencesHere) addReference(referenceLocation, relatedSymbol, state);
break;
case SpecialSearchKind.Constructor:
addConstructorReferences(referenceLocation, sourceFile, search, state);
@@ -882,7 +874,7 @@ namespace ts.FindAllReferences.Core {
getImportOrExportReferences(referenceLocation, referenceSymbol, search, state);
}
function getReferencesAtExportSpecifier(referenceLocation: Identifier, referenceSymbol: Symbol, exportSpecifier: ExportSpecifier, search: Search, state: State): void {
function getReferencesAtExportSpecifier(referenceLocation: Identifier, referenceSymbol: Symbol, exportSpecifier: ExportSpecifier, search: Search, state: State, addReferencesHere: boolean): void {
const { parent, propertyName, name } = exportSpecifier;
const exportDeclaration = parent.parent;
const localSymbol = getLocalSymbolForExportSpecifier(referenceLocation, referenceSymbol, exportSpecifier, state.checker);
@@ -900,7 +892,7 @@ namespace ts.FindAllReferences.Core {
addRef();
}
if (!state.options.isForRename && state.markSeenReExportRHS(name)) {
if (addReferencesHere && !state.options.isForRename && state.markSeenReExportRHS(name)) {
addReference(name, referenceSymbol, state);
}
}
@@ -925,7 +917,7 @@ namespace ts.FindAllReferences.Core {
}
function addRef() {
addReference(referenceLocation, localSymbol, state);
if (addReferencesHere) addReference(referenceLocation, localSymbol, state);
}
}
@@ -1208,63 +1200,31 @@ namespace ts.FindAllReferences.Core {
* distinction between structurally compatible implementations and explicit implementations, so we
* must use the AST.
*
* @param child A class or interface Symbol
* @param symbol A class or interface Symbol
* @param parent Another class or interface Symbol
* @param cachedResults A map of symbol id pairs (i.e. "child,parent") to booleans indicating previous results
*/
function explicitlyInheritsFrom(child: Symbol, parent: Symbol, cachedResults: Map<boolean>, checker: TypeChecker): boolean {
const parentIsInterface = parent.getFlags() & SymbolFlags.Interface;
return searchHierarchy(child);
function searchHierarchy(symbol: Symbol): boolean {
if (symbol === parent) {
return true;
}
const key = getSymbolId(symbol) + "," + getSymbolId(parent);
const cached = cachedResults.get(key);
if (cached !== undefined) {
return cached;
}
// Set the key so that we don't infinitely recurse
cachedResults.set(key, false);
const inherits = forEach(symbol.getDeclarations(), declaration => {
if (isClassLike(declaration)) {
if (parentIsInterface) {
const interfaceReferences = getClassImplementsHeritageClauseElements(declaration);
if (interfaceReferences) {
for (const typeReference of interfaceReferences) {
if (searchTypeReference(typeReference)) {
return true;
}
}
}
}
return searchTypeReference(getClassExtendsHeritageClauseElement(declaration));
}
else if (declaration.kind === SyntaxKind.InterfaceDeclaration) {
if (parentIsInterface) {
return forEach(getInterfaceBaseTypeNodes(<InterfaceDeclaration>declaration), searchTypeReference);
}
}
return false;
});
cachedResults.set(key, inherits);
return inherits;
function explicitlyInheritsFrom(symbol: Symbol, parent: Symbol, cachedResults: Map<boolean>, checker: TypeChecker): boolean {
if (symbol === parent) {
return true;
}
function searchTypeReference(typeReference: ExpressionWithTypeArguments): boolean {
if (typeReference) {
const key = getSymbolId(symbol) + "," + getSymbolId(parent);
const cached = cachedResults.get(key);
if (cached !== undefined) {
return cached;
}
// Set the key so that we don't infinitely recurse
cachedResults.set(key, false);
const inherits = symbol.declarations.some(declaration =>
getAllSuperTypeNodes(declaration).some(typeReference => {
const type = checker.getTypeAtLocation(typeReference);
if (type && type.symbol) {
return searchHierarchy(type.symbol);
}
}
return false;
}
return !!type && !!type.symbol && explicitlyInheritsFrom(type.symbol, parent, cachedResults, checker);
}));
cachedResults.set(key, inherits);
return inherits;
}
function getReferencesForSuperKeyword(superKeyword: Node): SymbolAndEntries[] {
@@ -1499,10 +1459,6 @@ namespace ts.FindAllReferences.Core {
* The value of previousIterationSymbol is undefined when the function is first called.
*/
function getPropertySymbolsFromBaseTypes(symbol: Symbol, propertyName: string, result: Push<Symbol>, previousIterationSymbolsCache: SymbolTable, checker: TypeChecker): void {
if (!symbol) {
return;
}
// If the current symbol is the same as the previous-iteration symbol, we can just return the symbol that has already been visited
// This is particularly important for the following cases, so that we do not infinitely visit the same symbol.
// For example:
@@ -1514,27 +1470,16 @@ namespace ts.FindAllReferences.Core {
// the function will add any found symbol of the property-name, then its sub-routine will call
// getPropertySymbolsFromBaseTypes again to walk up any base types to prevent revisiting already
// visited symbol, interface "C", the sub-routine will pass the current symbol as previousIterationSymbol.
if (previousIterationSymbolsCache.has(symbol.escapedName)) {
if (!symbol || previousIterationSymbolsCache.has(symbol.escapedName)) {
return;
}
if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
forEach(symbol.getDeclarations(), declaration => {
if (isClassLike(declaration)) {
getPropertySymbolFromTypeReference(getClassExtendsHeritageClauseElement(<ClassDeclaration>declaration));
forEach(getClassImplementsHeritageClauseElements(<ClassDeclaration>declaration), getPropertySymbolFromTypeReference);
}
else if (declaration.kind === SyntaxKind.InterfaceDeclaration) {
forEach(getInterfaceBaseTypeNodes(<InterfaceDeclaration>declaration), getPropertySymbolFromTypeReference);
}
});
}
return;
for (const declaration of symbol.declarations) {
for (const typeReference of getAllSuperTypeNodes(declaration)) {
const type = checker.getTypeAtLocation(typeReference);
if (!type) continue;
function getPropertySymbolFromTypeReference(typeReference: ExpressionWithTypeArguments): void {
if (typeReference) {
const type = checker.getTypeAtLocation(typeReference);
if (type) {
const propertySymbol = checker.getPropertyOfType(type, propertyName);
if (propertySymbol) {
result.push(...checker.getRootSymbols(propertySymbol));
+6 -8
View File
@@ -394,18 +394,16 @@ namespace ts.FindAllReferences {
case SyntaxKind.ExportDeclaration:
case SyntaxKind.ImportDeclaration: {
const decl = statement as ImportDeclaration | ExportDeclaration;
if (decl.moduleSpecifier && decl.moduleSpecifier.kind === SyntaxKind.StringLiteral) {
action(decl, decl.moduleSpecifier as StringLiteral);
if (decl.moduleSpecifier && isStringLiteral(decl.moduleSpecifier)) {
action(decl, decl.moduleSpecifier);
}
break;
}
case SyntaxKind.ImportEqualsDeclaration: {
const decl = statement as ImportEqualsDeclaration;
const { moduleReference } = decl;
if (moduleReference.kind === SyntaxKind.ExternalModuleReference &&
moduleReference.expression.kind === SyntaxKind.StringLiteral) {
action(decl, moduleReference.expression as StringLiteral);
if (isExternalModuleImportEquals(decl)) {
action(decl, decl.moduleReference.expression);
}
break;
}
@@ -647,7 +645,7 @@ namespace ts.FindAllReferences {
return node.kind === SyntaxKind.ModuleDeclaration && (node as ModuleDeclaration).name.kind === SyntaxKind.StringLiteral;
}
function isExternalModuleImportEquals({ moduleReference }: ImportEqualsDeclaration): boolean {
return moduleReference.kind === SyntaxKind.ExternalModuleReference && moduleReference.expression.kind === SyntaxKind.StringLiteral;
function isExternalModuleImportEquals(eq: ImportEqualsDeclaration): eq is ImportEqualsDeclaration & { moduleReference: { expression: StringLiteral } } {
return eq.moduleReference.kind === SyntaxKind.ExternalModuleReference && eq.moduleReference.expression.kind === SyntaxKind.StringLiteral;
}
}
+42 -43
View File
@@ -5,33 +5,40 @@ namespace ts.Completions.PathCompletions {
readonly kind: ScriptElementKind.scriptElement | ScriptElementKind.directory | ScriptElementKind.externalModuleName;
}
export interface PathCompletion extends NameAndKind {
readonly span: TextSpan;
}
function createPathCompletion(name: string, kind: PathCompletion["kind"], span: TextSpan): PathCompletion {
return { name, kind, span };
readonly span: TextSpan | undefined;
}
export function getStringLiteralCompletionsFromModuleNames(sourceFile: SourceFile, node: LiteralExpression, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): PathCompletion[] {
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(node, compilerOptions, host, typeChecker));
}
function getStringLiteralCompletionsFromModuleNamesWorker(node: LiteralExpression, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): ReadonlyArray<NameAndKind> {
const literalValue = normalizeSlashes(node.text);
const scriptPath = node.getSourceFile().path;
const scriptDirectory = getDirectoryPath(scriptPath);
const span = getDirectoryFragmentTextSpan(node.text, node.getStart(sourceFile) + 1);
if (isPathRelativeToScript(literalValue) || isRootedDiskPath(literalValue)) {
const extensions = getSupportedExtensions(compilerOptions);
if (compilerOptions.rootDirs) {
return getCompletionEntriesForDirectoryFragmentWithRootDirs(
compilerOptions.rootDirs, literalValue, scriptDirectory, extensions, /*includeExtensions*/ false, span, compilerOptions, host, scriptPath);
compilerOptions.rootDirs, literalValue, scriptDirectory, extensions, /*includeExtensions*/ false, compilerOptions, host, scriptPath);
}
else {
return getCompletionEntriesForDirectoryFragment(
literalValue, scriptDirectory, extensions, /*includeExtensions*/ false, span, host, scriptPath);
return getCompletionEntriesForDirectoryFragment(literalValue, scriptDirectory, extensions, /*includeExtensions*/ false, host, scriptPath);
}
}
else {
// Check for node modules
return getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, span, compilerOptions, host, typeChecker);
return getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, compilerOptions, host, typeChecker);
}
}
@@ -54,15 +61,15 @@ namespace ts.Completions.PathCompletions {
compareStringsCaseSensitive);
}
function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: ReadonlyArray<string>, includeExtensions: boolean, span: TextSpan, compilerOptions: CompilerOptions, host: LanguageServiceHost, exclude?: string): PathCompletion[] {
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: PathCompletion[] = [];
const result: NameAndKind[] = [];
for (const baseDirectory of baseDirectories) {
getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensions, includeExtensions, span, host, exclude, result);
getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensions, includeExtensions, host, exclude, result);
}
return result;
@@ -71,7 +78,7 @@ namespace ts.Completions.PathCompletions {
/**
* 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, span: TextSpan, host: LanguageServiceHost, exclude?: string, result: PathCompletion[] = []): PathCompletion[] {
function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: ReadonlyArray<string>, includeExtensions: boolean, host: LanguageServiceHost, exclude?: string, result: NameAndKind[] = []): NameAndKind[] {
if (fragment === undefined) {
fragment = "";
}
@@ -120,7 +127,7 @@ namespace ts.Completions.PathCompletions {
}
forEachKey(foundFiles, foundFile => {
result.push(createPathCompletion(foundFile, ScriptElementKind.scriptElement, span));
result.push(nameAndKind(foundFile, ScriptElementKind.scriptElement));
});
}
@@ -131,7 +138,7 @@ namespace ts.Completions.PathCompletions {
for (const directory of directories) {
const directoryName = getBaseFileName(normalizePath(directory));
result.push(createPathCompletion(directoryName, ScriptElementKind.directory, span));
result.push(nameAndKind(directoryName, ScriptElementKind.directory));
}
}
}
@@ -146,16 +153,16 @@ namespace ts.Completions.PathCompletions {
* 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, span: TextSpan, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): PathCompletion[] {
function getCompletionEntriesForNonRelativeModules(fragment: string, scriptPath: string, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): NameAndKind[] {
const { baseUrl, paths } = compilerOptions;
const result: PathCompletion[] = [];
const result: NameAndKind[] = [];
const fileExtensions = getSupportedExtensions(compilerOptions);
if (baseUrl) {
const projectDir = compilerOptions.project || host.getCurrentDirectory();
const absolute = isRootedDiskPath(baseUrl) ? baseUrl : combinePaths(projectDir, baseUrl);
getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/ false, span, host, /*exclude*/ undefined, result);
getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/ false, host, /*exclude*/ undefined, result);
for (const path in paths) {
const patterns = paths[path];
@@ -163,7 +170,7 @@ namespace ts.Completions.PathCompletions {
for (const { name, kind } of getCompletionsForPathMapping(path, patterns, fragment, baseUrl, 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(createPathCompletion(name, kind, span));
result.push(nameAndKind(name, kind));
}
}
}
@@ -174,15 +181,15 @@ namespace ts.Completions.PathCompletions {
forEachAncestorDirectory(scriptPath, ancestor => {
const nodeModules = combinePaths(ancestor, "node_modules");
if (host.directoryExists(nodeModules)) {
getCompletionEntriesForDirectoryFragment(fragment, nodeModules, fileExtensions, /*includeExtensions*/ false, span, host, /*exclude*/ undefined, result);
getCompletionEntriesForDirectoryFragment(fragment, nodeModules, fileExtensions, /*includeExtensions*/ false, host, /*exclude*/ undefined, result);
}
});
}
getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, span, result);
getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, result);
for (const moduleName of enumeratePotentialNonRelativeModules(fragment, scriptPath, compilerOptions, typeChecker, host)) {
result.push(createPathCompletion(moduleName, ScriptElementKind.externalModuleName, span));
result.push(nameAndKind(moduleName, ScriptElementKind.externalModuleName));
}
return result;
@@ -296,7 +303,7 @@ namespace ts.Completions.PathCompletions {
return deduplicate(nonRelativeModuleNames, equateStringsCaseSensitive, compareStringsCaseSensitive);
}
export function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): PathCompletion[] | undefined {
export function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): ReadonlyArray<PathCompletion> | undefined {
const token = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false);
const commentRanges = getLeadingCommentRanges(sourceFile.text, token.pos);
const range = commentRanges && find(commentRanges, commentRange => position >= commentRange.pos && position <= commentRange.end);
@@ -311,23 +318,13 @@ namespace ts.Completions.PathCompletions {
const [, prefix, kind, toComplete] = match;
const scriptPath = getDirectoryPath(sourceFile.path);
switch (kind) {
case "path": {
// Give completions for a relative path
const span = getDirectoryFragmentTextSpan(toComplete, range.pos + prefix.length);
return getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(compilerOptions), /*includeExtensions*/ true, span, host, sourceFile.path);
}
case "types": {
// Give completions based on the typings available
const span = createTextSpan(range.pos + prefix.length, match[0].length - prefix.length);
return getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, span);
}
default:
return undefined;
}
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, span: TextSpan, result: PathCompletion[] = []): PathCompletion[] {
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) {
@@ -375,7 +372,7 @@ namespace ts.Completions.PathCompletions {
function pushResult(moduleName: string) {
if (!seen.has(moduleName)) {
result.push(createPathCompletion(moduleName, ScriptElementKind.externalModuleName, span));
result.push(nameAndKind(moduleName, ScriptElementKind.externalModuleName));
seen.set(moduleName, true);
}
}
@@ -445,10 +442,12 @@ namespace ts.Completions.PathCompletions {
}
// Replace everything after the last directory seperator that appears
function getDirectoryFragmentTextSpan(text: string, textStart: number): TextSpan {
const index = text.lastIndexOf(directorySeparator);
function getDirectoryFragmentTextSpan(text: string, textStart: number): TextSpan | undefined {
const index = Math.max(text.lastIndexOf(directorySeparator), text.lastIndexOf("\\"));
const offset = index !== -1 ? index + 1 : 0;
return { start: textStart + offset, length: text.length - offset };
// 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 ..)
+4 -4
View File
@@ -1053,7 +1053,7 @@ namespace ts.refactor.extractSymbol {
changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariable, /*blankLineBetween*/ true);
// Consume
changeTracker.replaceNode(context.file, node, localReference, textChanges.useNonAdjustedPositions);
changeTracker.replaceNode(context.file, node, localReference);
}
else {
const newVariableDeclaration = createVariableDeclaration(localNameText, variableType, initializer);
@@ -1070,7 +1070,7 @@ namespace ts.refactor.extractSymbol {
// Consume
const localReference = createIdentifier(localNameText);
changeTracker.replaceNode(context.file, node, localReference, textChanges.useNonAdjustedPositions);
changeTracker.replaceNode(context.file, node, localReference);
}
else if (node.parent.kind === SyntaxKind.ExpressionStatement && scope === findAncestor(node, isScope)) {
// If the parent is an expression statement and the target scope is the immediately enclosing one,
@@ -1078,7 +1078,7 @@ namespace ts.refactor.extractSymbol {
const newVariableStatement = createVariableStatement(
/*modifiers*/ undefined,
createVariableDeclarationList([newVariableDeclaration], NodeFlags.Const));
changeTracker.replaceNode(context.file, node.parent, newVariableStatement, textChanges.useNonAdjustedPositions);
changeTracker.replaceNode(context.file, node.parent, newVariableStatement);
}
else {
const newVariableStatement = createVariableStatement(
@@ -1101,7 +1101,7 @@ namespace ts.refactor.extractSymbol {
}
else {
const localReference = createIdentifier(localNameText);
changeTracker.replaceNode(context.file, node, localReference, textChanges.useNonAdjustedPositions);
changeTracker.replaceNode(context.file, node, localReference);
}
}
}
+73 -109
View File
@@ -54,7 +54,7 @@ namespace ts {
public jsDoc: JSDoc[];
public original: Node;
public transformFlags: TransformFlags;
private _children: Node[];
private _children: Node[] | undefined;
constructor(kind: SyntaxKind, pos: number, end: number) {
this.pos = pos;
@@ -117,106 +117,17 @@ namespace ts {
return sourceFile.text.substring(this.getStart(sourceFile), this.getEnd());
}
private addSyntheticNodes(nodes: Push<Node>, pos: number, end: number): number {
scanner.setTextPos(pos);
while (pos < end) {
const token = scanner.scan();
const textPos = scanner.getTextPos();
if (textPos <= end) {
if (token === SyntaxKind.Identifier) {
Debug.fail(`Did not expect ${Debug.showSyntaxKind(this)} to have an Identifier in its trivia`);
}
nodes.push(createNode(token, pos, textPos, this));
}
pos = textPos;
if (token === SyntaxKind.EndOfFileToken) {
break;
}
}
return pos;
}
private createSyntaxList(nodes: NodeArray<Node>): Node {
const list = <NodeObject>createNode(SyntaxKind.SyntaxList, nodes.pos, nodes.end, this);
list._children = [];
let pos = nodes.pos;
for (const node of nodes) {
if (pos < node.pos) {
pos = this.addSyntheticNodes(list._children, pos, node.pos);
}
list._children.push(node);
pos = node.end;
}
if (pos < nodes.end) {
this.addSyntheticNodes(list._children, pos, nodes.end);
}
return list;
}
private createChildren(sourceFile?: SourceFileLike) {
if (!isNodeKind(this.kind)) {
this._children = emptyArray;
return;
}
if (isJSDocCommentContainingNode(this)) {
/** Don't add trivia for "tokens" since this is in a comment. */
const children: Node[] = [];
this.forEachChild(child => { children.push(child); });
this._children = children;
return;
}
const children: Node[] = [];
scanner.setText((sourceFile || this.getSourceFile()).text);
let pos = this.pos;
const processNode = (node: Node) => {
pos = this.addSyntheticNodes(children, pos, node.pos);
children.push(node);
pos = node.end;
};
const processNodes = (nodes: NodeArray<Node>) => {
if (pos < nodes.pos) {
pos = this.addSyntheticNodes(children, pos, nodes.pos);
}
children.push(this.createSyntaxList(nodes));
pos = nodes.end;
};
// jsDocComments need to be the first children
if (this.jsDoc) {
for (const jsDocComment of this.jsDoc) {
processNode(jsDocComment);
}
}
// For syntactic classifications, all trivia are classcified together, including jsdoc comments.
// For that to work, the jsdoc comments should still be the leading trivia of the first child.
// Restoring the scanner position ensures that.
pos = this.pos;
forEachChild(this, processNode, processNodes);
if (pos < this.end) {
this.addSyntheticNodes(children, pos, this.end);
}
scanner.setText(undefined);
this._children = children;
}
public getChildCount(sourceFile?: SourceFile): number {
this.assertHasRealPosition();
if (!this._children) this.createChildren(sourceFile);
return this._children.length;
return this.getChildren(sourceFile).length;
}
public getChildAt(index: number, sourceFile?: SourceFile): Node {
this.assertHasRealPosition();
if (!this._children) this.createChildren(sourceFile);
return this._children[index];
return this.getChildren(sourceFile)[index];
}
public getChildren(sourceFile?: SourceFileLike): Node[] {
this.assertHasRealPosition("Node without a real position cannot be scanned and thus has no token nodes - use forEachChild and collect the result if that's fine");
if (!this._children) this.createChildren(sourceFile);
return this._children;
return this._children || (this._children = createChildren(this, sourceFile));
}
public getFirstToken(sourceFile?: SourceFile): Node {
@@ -249,6 +160,74 @@ namespace ts {
}
}
function createChildren(node: Node, sourceFile: SourceFileLike | undefined): Node[] {
if (!isNodeKind(node.kind)) {
return emptyArray;
}
const children: Node[] = [];
if (isJSDocCommentContainingNode(node)) {
/** Don't add trivia for "tokens" since this is in a comment. */
node.forEachChild(child => { children.push(child); });
return children;
}
scanner.setText((sourceFile || node.getSourceFile()).text);
let pos = node.pos;
const processNode = (child: Node) => {
addSyntheticNodes(children, pos, child.pos, node);
children.push(child);
pos = child.end;
};
const processNodes = (nodes: NodeArray<Node>) => {
addSyntheticNodes(children, pos, nodes.pos, node);
children.push(createSyntaxList(nodes, node));
pos = nodes.end;
};
// jsDocComments need to be the first children
forEach((node as JSDocContainer).jsDoc, processNode);
// For syntactic classifications, all trivia are classified together, including jsdoc comments.
// For that to work, the jsdoc comments should still be the leading trivia of the first child.
// Restoring the scanner position ensures that.
pos = node.pos;
node.forEachChild(processNode, processNodes);
addSyntheticNodes(children, pos, node.end, node);
scanner.setText(undefined);
return children;
}
function addSyntheticNodes(nodes: Push<Node>, pos: number, end: number, parent: Node): void {
scanner.setTextPos(pos);
while (pos < end) {
const token = scanner.scan();
const textPos = scanner.getTextPos();
if (textPos <= end) {
if (token === SyntaxKind.Identifier) {
Debug.fail(`Did not expect ${Debug.showSyntaxKind(parent)} to have an Identifier in its trivia`);
}
nodes.push(createNode(token, pos, textPos, parent));
}
pos = textPos;
if (token === SyntaxKind.EndOfFileToken) {
break;
}
}
}
function createSyntaxList(nodes: NodeArray<Node>, parent: Node): Node {
const list = createNode(SyntaxKind.SyntaxList, nodes.pos, nodes.end, parent) as any as SyntaxList;
list._children = [];
let pos = nodes.pos;
for (const node of nodes) {
addSyntheticNodes(list._children, pos, node.pos, parent);
list._children.push(node);
pos = node.end;
}
addSyntheticNodes(list._children, pos, nodes.end, parent);
return list;
}
class TokenOrIdentifierObject implements Node {
public kind: SyntaxKind;
public pos: number;
@@ -576,7 +555,7 @@ namespace ts {
*/
function findInheritedJSDocComments(declaration: Declaration, propertyName: string, typeChecker: TypeChecker): SymbolDisplayPart[] {
let foundDocs = false;
return flatMap(getAllSuperTypeNodes(declaration), superTypeNode => {
return flatMap(declaration.parent ? getAllSuperTypeNodes(declaration.parent) : emptyArray, superTypeNode => {
if (foundDocs) {
return emptyArray;
}
@@ -594,21 +573,6 @@ namespace ts {
});
}
/**
* Finds and returns the `TypeNode` for all super classes and implemented interfaces given a declaration.
* @param declaration The possibly-inherited declaration.
* @returns A filled array of `TypeNode`s containing all super classes and implemented interfaces if any exist, otherwise an empty array.
*/
function getAllSuperTypeNodes(declaration: Declaration): ReadonlyArray<TypeNode> {
const container = declaration.parent;
if (!container || (!isClassDeclaration(container) && !isInterfaceDeclaration(container))) {
return emptyArray;
}
const extended = getClassExtendsHeritageClauseElement(container);
const types = extended ? [extended] : emptyArray;
return isClassLike(container) ? concatenate(types, getClassImplementsHeritageClauseElements(container)) : types;
}
class SourceFileObject extends NodeObject implements SourceFile {
public kind: SyntaxKind.SourceFile;
public _declarationBrand: any;
+64 -76
View File
@@ -103,10 +103,11 @@ namespace ts.textChanges {
enum ChangeKind {
Remove,
ReplaceWithSingleNode,
ReplaceWithMultipleNodes
ReplaceWithMultipleNodes,
Text,
}
type Change = ReplaceWithSingleNode | ReplaceWithMultipleNodes | RemoveNode;
type Change = ReplaceWithSingleNode | ReplaceWithMultipleNodes | RemoveNode | ChangeText;
interface BaseChange {
readonly sourceFile: SourceFile;
@@ -132,6 +133,15 @@ namespace ts.textChanges {
readonly options?: InsertNodeOptions;
}
interface ChangeText extends BaseChange {
readonly kind: ChangeKind.Text;
readonly text: string;
}
function getAdjustedRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd): TextRange {
return { pos: getAdjustedStartPosition(sourceFile, startNode, options, Position.Start), end: getAdjustedEndPosition(sourceFile, endNode, options) };
}
function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStart, position: Position) {
if (options.useNonAdjustedStartPosition) {
return node.getStart(sourceFile);
@@ -212,7 +222,7 @@ namespace ts.textChanges {
}
/** Public for tests only. Other callers should use `ChangeTracker.with`. */
constructor(public readonly newLineCharacter: string, private readonly formatContext: formatting.FormatContext) {}
constructor(private readonly newLineCharacter: string, private readonly formatContext: formatting.FormatContext) {}
public deleteRange(sourceFile: SourceFile, range: TextRange) {
this.changes.push({ kind: ChangeKind.Remove, sourceFile, range });
@@ -280,46 +290,34 @@ namespace ts.textChanges {
return this;
}
// TODO (https://github.com/Microsoft/TypeScript/issues/21246): default should probably be useNonAdjustedPositions
public replaceRange(sourceFile: SourceFile, range: TextRange, newNode: Node, options: InsertNodeOptions = {}) {
this.changes.push({ kind: ChangeKind.ReplaceWithSingleNode, sourceFile, range, options, node: newNode });
return this;
}
// TODO (https://github.com/Microsoft/TypeScript/issues/21246): default should probably be useNonAdjustedPositions
public replaceNode(sourceFile: SourceFile, oldNode: Node, newNode: Node, options: ChangeNodeOptions = {}) {
const pos = getAdjustedStartPosition(sourceFile, oldNode, options, Position.Start);
const end = getAdjustedEndPosition(sourceFile, oldNode, options);
return this.replaceRange(sourceFile, { pos, end }, newNode, options);
public replaceNode(sourceFile: SourceFile, oldNode: Node, newNode: Node, options: ChangeNodeOptions = useNonAdjustedPositions) {
return this.replaceRange(sourceFile, getAdjustedRange(sourceFile, oldNode, oldNode, options), newNode, options);
}
// TODO (https://github.com/Microsoft/TypeScript/issues/21246): default should probably be useNonAdjustedPositions
public replaceNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, newNode: Node, options: ChangeNodeOptions = {}) {
const pos = getAdjustedStartPosition(sourceFile, startNode, options, Position.Start);
const end = getAdjustedEndPosition(sourceFile, endNode, options);
return this.replaceRange(sourceFile, { pos, end }, newNode, options);
public replaceNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, newNode: Node, options: ChangeNodeOptions = useNonAdjustedPositions) {
this.replaceRange(sourceFile, getAdjustedRange(sourceFile, startNode, endNode, options), newNode, options);
}
public replaceRangeWithNodes(sourceFile: SourceFile, range: TextRange, newNodes: ReadonlyArray<Node>, options: InsertNodeOptions = {}) {
private replaceRangeWithNodes(sourceFile: SourceFile, range: TextRange, newNodes: ReadonlyArray<Node>, options: InsertNodeOptions = {}) {
this.changes.push({ kind: ChangeKind.ReplaceWithMultipleNodes, sourceFile, range, options, nodes: newNodes });
return this;
}
public replaceNodeWithNodes(sourceFile: SourceFile, oldNode: Node, newNodes: ReadonlyArray<Node>, options: ChangeNodeOptions = useNonAdjustedPositions) {
const pos = getAdjustedStartPosition(sourceFile, oldNode, options, Position.Start);
const end = getAdjustedEndPosition(sourceFile, oldNode, options);
return this.replaceRangeWithNodes(sourceFile, { pos, end }, newNodes, options);
return this.replaceRangeWithNodes(sourceFile, getAdjustedRange(sourceFile, oldNode, oldNode, options), newNodes, options);
}
public replaceNodeRangeWithNodes(sourceFile: SourceFile, startNode: Node, endNode: Node, newNodes: ReadonlyArray<Node>, options: ChangeNodeOptions = useNonAdjustedPositions) {
const pos = getAdjustedStartPosition(sourceFile, startNode, options, Position.Start);
const end = getAdjustedEndPosition(sourceFile, endNode, options);
return this.replaceRangeWithNodes(sourceFile, { pos, end }, newNodes, options);
return this.replaceRangeWithNodes(sourceFile, getAdjustedRange(sourceFile, startNode, endNode, options), newNodes, options);
}
private insertNodeAt(sourceFile: SourceFile, pos: number, newNode: Node, options: InsertNodeOptions = {}) {
this.changes.push({ kind: ChangeKind.ReplaceWithSingleNode, sourceFile, options, node: newNode, range: { pos, end: pos } });
return this;
this.replaceRange(sourceFile, createTextRange(pos), newNode, options);
}
private insertNodesAt(sourceFile: SourceFile, pos: number, newNodes: ReadonlyArray<Node>, options: InsertNodeOptions = {}): void {
@@ -344,6 +342,23 @@ namespace ts.textChanges {
this.replaceRange(sourceFile, { pos, end: pos }, createToken(modifier), { suffix: " " });
}
public insertCommentBeforeLine(sourceFile: SourceFile, lineNumber: number, position: number, commentText: string): void {
const lineStartPosition = getStartPositionOfLine(lineNumber, sourceFile);
const startPosition = getFirstNonSpaceCharacterPosition(sourceFile.text, lineStartPosition);
// First try to see if we can put the comment on the previous line.
// We need to make sure that we are not in the middle of a string literal or a comment.
// If so, we do not want to separate the node from its comment if we can.
// Otherwise, add an extra new line immediately before the error span.
const insertAtLineStart = isValidLocationToAddComment(sourceFile, startPosition);
const token = getTouchingToken(sourceFile, insertAtLineStart ? startPosition : position, /*includeJsDocComment*/ false);
const text = `${insertAtLineStart ? "" : this.newLineCharacter}${sourceFile.text.slice(lineStartPosition, startPosition)}//${commentText}${this.newLineCharacter}`;
this.insertText(sourceFile, token.getStart(sourceFile), text);
}
private insertText(sourceFile: SourceFile, pos: number, text: string): void {
this.changes.push({ kind: ChangeKind.Text, sourceFile, range: { pos, end: pos }, text });
}
/** Prefer this over replacing a node with another that has a type annotation, as it avoids reformatting the other parts of the node. */
public insertTypeAnnotation(sourceFile: SourceFile, node: TypeAnnotatable, type: TypeNode): void {
const end = (isFunctionLike(node)
@@ -359,7 +374,7 @@ namespace ts.textChanges {
this.insertNodesAt(sourceFile, start, typeParameters, { prefix: "<", suffix: ">" });
}
private getOptionsForInsertNodeBefore(before: Node, doubleNewlines: boolean): ChangeNodeOptions {
private getOptionsForInsertNodeBefore(before: Node, doubleNewlines: boolean): InsertNodeOptions {
if (isStatement(before) || isClassElement(before)) {
return { suffix: doubleNewlines ? this.newLineCharacter + this.newLineCharacter : this.newLineCharacter };
}
@@ -393,7 +408,7 @@ namespace ts.textChanges {
}
private replaceConstructorBody(sourceFile: SourceFile, ctr: ConstructorDeclaration, statements: ReadonlyArray<Statement>): void {
this.replaceNode(sourceFile, ctr.body, createBlock(statements, /*multiLine*/ true), { useNonAdjustedEndPosition: true });
this.replaceNode(sourceFile, ctr.body, createBlock(statements, /*multiLine*/ true));
}
public insertNodeAtEndOfScope(sourceFile: SourceFile, scope: Node, newNode: Node): void {
@@ -430,17 +445,11 @@ namespace ts.textChanges {
// check if previous statement ends with semicolon
// if not - insert semicolon to preserve the code from changing the meaning due to ASI
if (sourceFile.text.charCodeAt(after.end - 1) !== CharacterCodes.semicolon) {
this.changes.push({
kind: ChangeKind.ReplaceWithSingleNode,
sourceFile,
options: {},
range: { pos: after.end, end: after.end },
node: createToken(SyntaxKind.SemicolonToken)
});
this.replaceRange(sourceFile, createTextRange(after.end), createToken(SyntaxKind.SemicolonToken));
}
}
const endPosition = getAdjustedEndPosition(sourceFile, after, {});
return this.replaceRange(sourceFile, { pos: endPosition, end: endPosition }, newNode, this.getInsertNodeAfterOptions(after));
return this.replaceRange(sourceFile, createTextRange(endPosition), newNode, this.getInsertNodeAfterOptions(after));
}
private getInsertNodeAfterOptions(node: Node): InsertNodeOptions {
@@ -524,17 +533,9 @@ namespace ts.textChanges {
startPos = getStartPositionOfLine(lineAndCharOfNextElement.line, sourceFile);
}
this.changes.push({
kind: ChangeKind.ReplaceWithSingleNode,
sourceFile,
range: { pos: startPos, end: containingList[index + 1].getStart(sourceFile) },
node: newNode,
options: {
prefix,
// write separator and leading trivia of the next element as suffix
suffix: `${tokenToString(nextToken.kind)}${sourceFile.text.substring(nextToken.end, containingList[index + 1].getStart(sourceFile))}`
}
});
// write separator and leading trivia of the next element as suffix
const suffix = `${tokenToString(nextToken.kind)}${sourceFile.text.substring(nextToken.end, containingList[index + 1].getStart(sourceFile))}`;
this.replaceRange(sourceFile, createTextRange(startPos, containingList[index + 1].getStart(sourceFile)), newNode, { prefix, suffix });
}
}
else {
@@ -568,13 +569,7 @@ namespace ts.textChanges {
}
if (multilineList) {
// insert separator immediately following the 'after' node to preserve comments in trailing trivia
this.changes.push({
kind: ChangeKind.ReplaceWithSingleNode,
sourceFile,
range: { pos: end, end },
node: createToken(separator),
options: {}
});
this.replaceRange(sourceFile, createTextRange(end), createToken(separator));
// use the same indentation as 'after' item
const indentation = formatting.SmartIndenter.findFirstNonWhitespaceColumn(afterStartLinePosition, afterStart, sourceFile, this.formatContext.options);
// insert element before the line break on the line that contains 'after' element
@@ -582,22 +577,10 @@ namespace ts.textChanges {
if (insertPos !== end && isLineBreak(sourceFile.text.charCodeAt(insertPos - 1))) {
insertPos--;
}
this.changes.push({
kind: ChangeKind.ReplaceWithSingleNode,
sourceFile,
range: { pos: insertPos, end: insertPos },
node: newNode,
options: { indentation, prefix: this.newLineCharacter }
});
this.replaceRange(sourceFile, createTextRange(insertPos), newNode, { indentation, prefix: this.newLineCharacter });
}
else {
this.changes.push({
kind: ChangeKind.ReplaceWithSingleNode,
sourceFile,
range: { pos: end, end },
node: newNode,
options: { prefix: `${tokenToString(separator)} ` }
});
this.replaceRange(sourceFile, createTextRange(end), newNode, { prefix: `${tokenToString(separator)} ` });
}
}
return this;
@@ -608,7 +591,7 @@ namespace ts.textChanges {
const newCls = cls.kind === SyntaxKind.ClassDeclaration
? updateClassDeclaration(cls, cls.decorators, cls.modifiers, cls.name, cls.typeParameters, cls.heritageClauses, members)
: updateClassExpression(cls, cls.modifiers, cls.name, cls.typeParameters, cls.heritageClauses, members);
this.replaceNode(sourceFile, cls, newCls, { useNonAdjustedEndPosition: true });
this.replaceNode(sourceFile, cls, newCls);
});
}
@@ -647,6 +630,9 @@ namespace ts.textChanges {
if (change.kind === ChangeKind.Remove) {
return "";
}
if (change.kind === ChangeKind.Text) {
return change.text;
}
const { options = {}, range: { pos } } = change;
const format = (n: Node) => getFormattedTextOfNode(n, sourceFile, pos, options, newLineCharacter, formatContext, validate);
@@ -659,20 +645,18 @@ namespace ts.textChanges {
}
/** Note: this may mutate `nodeIn`. */
function getFormattedTextOfNode(nodeIn: Node, sourceFile: SourceFile, pos: number, options: ChangeNodeOptions, newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText): string {
function getFormattedTextOfNode(nodeIn: Node, sourceFile: SourceFile, pos: number, { indentation, prefix, delta }: InsertNodeOptions, newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText): string {
const { node, text } = getNonformattedText(nodeIn, sourceFile, newLineCharacter);
if (validate) validate(node, text);
const { options: formatOptions } = formatContext;
const initialIndentation =
options.indentation !== undefined
? options.indentation
: formatting.SmartIndenter.getIndentation(pos, sourceFile, formatOptions, options.prefix === newLineCharacter || getLineStartPositionForPosition(pos, sourceFile) === pos);
const delta =
options.delta !== undefined
? options.delta
: formatting.SmartIndenter.shouldIndentChildNode(formatContext.options, nodeIn)
? (formatOptions.indentSize || 0)
: 0;
indentation !== undefined
? indentation
: formatting.SmartIndenter.getIndentation(pos, sourceFile, formatOptions, prefix === newLineCharacter || getLineStartPositionForPosition(pos, sourceFile) === pos);
if (delta === undefined) {
delta = formatting.SmartIndenter.shouldIndentChildNode(formatContext.options, nodeIn) ? (formatOptions.indentSize || 0) : 0;
}
const file: SourceFileLike = { text, getLineAndCharacterOfPosition(pos) { return getLineAndCharacterOfPosition(this, pos); } };
const changes = formatting.formatNodeGivenIndentation(node, file, sourceFile.languageVariant, initialIndentation, delta, formatContext);
return applyChanges(text, changes);
@@ -896,4 +880,8 @@ namespace ts.textChanges {
}
}
}
export function isValidLocationToAddComment(sourceFile: SourceFile, position: number) {
return !isInComment(sourceFile, position) && !isInString(sourceFile, position) && !isInTemplateString(sourceFile, position);
}
}
+1
View File
@@ -440,6 +440,7 @@ namespace ts {
* This may be omitted to indicate that the code fix can't be applied in a group.
*/
fixId?: {};
fixAllDescription?: string;
}
export interface CombinedCodeActions {
+4
View File
@@ -1216,6 +1216,10 @@ namespace ts {
}
return result;
}
export function skipConstraint(type: Type): Type {
return type.flags & TypeFlags.TypeParameter ? type.getConstraint() : type;
}
}
// Display-part writer helpers