Merge branch 'master' into js-object-literal-assignments-as-declarations

This commit is contained in:
Nathan Shively-Sanders
2018-02-23 09:24:32 -08:00
1006 changed files with 83936 additions and 59455 deletions
@@ -14,18 +14,22 @@ namespace ts.codefix {
return changes.length === 0 ? undefined : [{ description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class), changes, fixId }];
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
addMissingMembers(getClass(diag.file!, diag.start!), context.sourceFile, context.program.getTypeChecker(), changes);
}),
getAllCodeActions: context => {
const seenClassDeclarations = createMap<true>();
return codeFixAll(context, errorCodes, (changes, diag) => {
const classDeclaration = getClass(diag.file!, diag.start!);
if (addToSeen(seenClassDeclarations, getNodeId(classDeclaration))) {
addMissingMembers(classDeclaration, context.sourceFile, context.program.getTypeChecker(), changes);
}
});
},
});
function getClass(sourceFile: SourceFile, pos: number): ClassLikeDeclaration {
// This is the identifier in the case of a class declaration
// Token is the identifier in the case of a class declaration
// or the class keyword token in the case of a class expression.
const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false);
const classDeclaration = token.parent;
Debug.assert(isClassLike(classDeclaration));
return classDeclaration as ClassLikeDeclaration;
return cast(token.parent, isClassLike);
}
function addMissingMembers(classDeclaration: ClassLikeDeclaration, sourceFile: SourceFile, checker: TypeChecker, changeTracker: textChanges.ChangeTracker): void {
@@ -31,9 +31,7 @@ namespace ts.codefix {
});
function getClass(sourceFile: SourceFile, pos: number): ClassLikeDeclaration {
const classDeclaration = getContainingClass(getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false));
Debug.assert(!!classDeclaration);
return classDeclaration!;
return Debug.assertDefined(getContainingClass(getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false)));
}
function addMissingDeclarations(
@@ -0,0 +1,142 @@
/* @internal */
namespace ts.codefix {
const fixIdAddDefiniteAssignmentAssertions = "addMissingPropertyDefiniteAssignmentAssertions";
const fixIdAddUndefinedType = "addMissingPropertyUndefinedType";
const fixIdAddInitializer = "addMissingPropertyInitializer";
const errorCodes = [Diagnostics.Property_0_has_no_initializer_and_is_not_definitely_assigned_in_the_constructor.code];
registerCodeFix({
errorCodes,
getCodeActions: (context) => {
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)
];
append(result, getActionForAddMissingInitializer(context, propertyDeclaration, newLineCharacter));
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);
break;
case fixIdAddUndefinedType:
addUndefinedType(changes, diag.file, propertyDeclaration);
break;
case fixIdAddInitializer:
const checker = context.program.getTypeChecker();
const initializer = getInitializer(checker, propertyDeclaration);
if (!initializer) return;
addInitializer(changes, diag.file, propertyDeclaration, initializer, newLineCharacter);
break;
default:
Debug.fail(JSON.stringify(context.fixId));
}
});
},
});
function getPropertyDeclaration (sourceFile: SourceFile, pos: number): PropertyDeclaration | undefined {
const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false);
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 addDefiniteAssignmentAssertion(changeTracker: textChanges.ChangeTracker, propertyDeclarationSourceFile: SourceFile, propertyDeclaration: PropertyDeclaration, newLineCharacter: string): void {
const property = updateProperty(
propertyDeclaration,
propertyDeclaration.decorators,
propertyDeclaration.modifiers,
propertyDeclaration.name,
createToken(SyntaxKind.ExclamationToken),
propertyDeclaration.type,
propertyDeclaration.initializer
);
changeTracker.replaceNode(propertyDeclarationSourceFile, propertyDeclaration, property, { suffix: newLineCharacter });
}
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 };
}
function addUndefinedType(changeTracker: textChanges.ChangeTracker, propertyDeclarationSourceFile: SourceFile, propertyDeclaration: PropertyDeclaration): void {
const undefinedTypeNode = createKeywordTypeNode(SyntaxKind.UndefinedKeyword);
const types = isUnionTypeNode(propertyDeclaration.type) ? propertyDeclaration.type.types.concat(undefinedTypeNode) : [propertyDeclaration.type, undefinedTypeNode];
changeTracker.replaceNode(propertyDeclarationSourceFile, propertyDeclaration.type, createUnionTypeNode(types));
}
function getActionForAddMissingInitializer (context: CodeFixContext, propertyDeclaration: PropertyDeclaration, newLineCharacter: string): 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 };
}
function addInitializer (changeTracker: textChanges.ChangeTracker, propertyDeclarationSourceFile: SourceFile, propertyDeclaration: PropertyDeclaration, initializer: Expression, newLineCharacter: string): void {
const property = updateProperty(
propertyDeclaration,
propertyDeclaration.decorators,
propertyDeclaration.modifiers,
propertyDeclaration.name,
propertyDeclaration.questionToken,
propertyDeclaration.type,
initializer
);
changeTracker.replaceNode(propertyDeclarationSourceFile, propertyDeclaration, property, { suffix: newLineCharacter });
}
function getInitializer(checker: TypeChecker, propertyDeclaration: PropertyDeclaration): Expression | undefined {
return getDefaultValueFromType(checker, checker.getTypeFromTypeNode(propertyDeclaration.type));
}
function getDefaultValueFromType (checker: TypeChecker, type: Type): Expression | undefined {
if (type.flags & TypeFlags.String) {
return createLiteral("");
}
else if (type.flags & TypeFlags.Number) {
return createNumericLiteral("0");
}
else if (type.flags & TypeFlags.Boolean) {
return createFalse();
}
else if (type.flags & TypeFlags.Literal) {
return createLiteral((<LiteralType>type).value);
}
else if (type.flags & TypeFlags.Union) {
return firstDefined((<UnionType>type).types, t => getDefaultValueFromType(checker, t));
}
else if (getObjectFlags(type) & ObjectFlags.Class) {
const classDeclaration = getClassLikeDeclarationOfSymbol(type.symbol);
if (!classDeclaration || hasModifier(classDeclaration, ModifierFlags.Abstract)) return undefined;
const constructorDeclaration = find<ClassElement, ConstructorDeclaration>(classDeclaration.members, (m): m is ConstructorDeclaration => isConstructorDeclaration(m) && !!m.body)!;
if (constructorDeclaration && constructorDeclaration.parameters.length) return undefined;
return createNew(createIdentifier(type.symbol.name), /*typeArguments*/ undefined, /*argumentsArray*/ undefined);
}
return undefined;
}
}
+1 -1
View File
@@ -17,4 +17,4 @@
/// <reference path='helpers.ts' />
/// <reference path='inferFromUsage.ts' />
/// <reference path="fixInvalidImportSyntax.ts" />
/// <reference path="fixStrictClassInitialization.ts" />
+10 -11
View File
@@ -106,7 +106,7 @@ namespace ts.Completions {
}
function completionInfoFromData(sourceFile: SourceFile, typeChecker: TypeChecker, compilerOptions: CompilerOptions, log: Log, completionData: CompletionData, includeInsertTextCompletions: boolean): CompletionInfo {
const { symbols, completionKind, isNewIdentifierLocation, location, propertyAccessToConvert, keywordFilters, symbolToOriginInfoMap, recommendedCompletion, isJsxInitializer } = completionData;
const { symbols, completionKind, isInSnippetScope, isNewIdentifierLocation, location, propertyAccessToConvert, keywordFilters, symbolToOriginInfoMap, recommendedCompletion, isJsxInitializer } = completionData;
if (sourceFile.languageVariant === LanguageVariant.JSX && location && location.parent && isJsxClosingElement(location.parent)) {
// In the TypeScript JSX element, if such element is not defined. When users query for completion at closing tag,
@@ -148,7 +148,7 @@ namespace ts.Completions {
addRange(entries, getKeywordCompletions(keywordFilters));
}
return { isGlobalCompletion: completionKind === CompletionKind.Global, isMemberCompletion, isNewIdentifierLocation, entries };
return { isGlobalCompletion: isInSnippetScope, isMemberCompletion, isNewIdentifierLocation, entries };
}
function isMemberCompletionKind(kind: CompletionKind): boolean {
@@ -635,6 +635,7 @@ namespace ts.Completions {
readonly kind: CompletionDataKind.Data;
readonly symbols: ReadonlyArray<Symbol>;
readonly completionKind: CompletionKind;
readonly isInSnippetScope: boolean;
/** Note that the presence of this alone doesn't mean that we need a conversion. Only do that if the completion is not an ordinary identifier. */
readonly propertyAccessToConvert: PropertyAccessExpression | undefined;
readonly isNewIdentifierLocation: boolean;
@@ -649,7 +650,6 @@ namespace ts.Completions {
const enum CompletionKind {
ObjectPropertyDeclaration,
/** Note that sometimes we access completions from global scope, but use "None" instead of this. See isGlobalCompletionScope. */
Global,
PropertyAccess,
MemberLike,
@@ -753,6 +753,7 @@ namespace ts.Completions {
log("getCompletionData: Is inside comment: " + (timestamp() - start));
let insideJsDocTagTypeExpression = false;
let isInSnippetScope = false;
if (insideComment) {
if (hasDocComment(sourceFile, position)) {
if (sourceFile.text.charCodeAt(position - 1) === CharacterCodes.at) {
@@ -971,7 +972,7 @@ namespace ts.Completions {
log("getCompletionData: Semantic work: " + (timestamp() - semanticStart));
const recommendedCompletion = previousToken && getRecommendedCompletion(previousToken, position, sourceFile, typeChecker);
return { kind: CompletionDataKind.Data, symbols, completionKind, propertyAccessToConvert, isNewIdentifierLocation, location, keywordFilters, symbolToOriginInfoMap, recommendedCompletion, previousToken, isJsxInitializer };
return { kind: CompletionDataKind.Data, symbols, completionKind, isInSnippetScope, propertyAccessToConvert, isNewIdentifierLocation, location, keywordFilters, symbolToOriginInfoMap, recommendedCompletion, previousToken, isJsxInitializer };
type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag;
@@ -1098,7 +1099,7 @@ namespace ts.Completions {
}
// Get all entities in the current scope.
completionKind = CompletionKind.None;
completionKind = CompletionKind.Global;
isNewIdentifierLocation = isNewIdentifierDefinitionLocation(contextToken);
if (previousToken !== contextToken) {
@@ -1134,9 +1135,7 @@ namespace ts.Completions {
position;
const scopeNode = getScopeNode(contextToken, adjustedPosition, sourceFile) || sourceFile;
if (isGlobalCompletionScope(scopeNode)) {
completionKind = CompletionKind.Global;
}
isInSnippetScope = isSnippetScope(scopeNode);
const symbolMeanings = SymbolFlags.Type | SymbolFlags.Value | SymbolFlags.Namespace | SymbolFlags.Alias;
@@ -1161,7 +1160,7 @@ namespace ts.Completions {
return true;
}
function isGlobalCompletionScope(scopeNode: Node): boolean {
function isSnippetScope(scopeNode: Node): boolean {
switch (scopeNode.kind) {
case SyntaxKind.SourceFile:
case SyntaxKind.TemplateExpression:
@@ -1999,7 +1998,7 @@ namespace ts.Completions {
// NOTE: if one only performs this step when m.name is an identifier,
// things like '__proto__' are not filtered out.
const name = getNameOfDeclaration(m);
existingName = getEscapedTextOfIdentifierOrLiteral(name as (Identifier | LiteralExpression));
existingName = isPropertyNameLiteral(name) ? getEscapedTextOfIdentifierOrLiteral(name) : undefined;
}
existingMemberNames.set(existingName, true);
@@ -2128,10 +2127,10 @@ namespace ts.Completions {
// TODO: GH#18169
return { name: JSON.stringify(name), needsConvertPropertyAccess: false };
case CompletionKind.PropertyAccess:
case CompletionKind.None:
case CompletionKind.Global:
// Don't add a completion for a name starting with a space. See https://github.com/Microsoft/TypeScript/pull/20547
return name.charCodeAt(0) === CharacterCodes.space ? undefined : { name, needsConvertPropertyAccess: true };
case CompletionKind.None:
case CompletionKind.String:
return validIdentiferResult;
default:
+4 -16
View File
@@ -21,23 +21,11 @@ namespace ts.DocumentHighlights {
};
}
function getSemanticDocumentHighlights(position: number, node: Node, program: Program, cancellationToken: CancellationToken, sourceFilesToSearch: ReadonlyArray<SourceFile>): DocumentHighlights[] {
function getSemanticDocumentHighlights(position: number, node: Node, program: Program, cancellationToken: CancellationToken, sourceFilesToSearch: ReadonlyArray<SourceFile>): DocumentHighlights[] | undefined {
const referenceEntries = FindAllReferences.getReferenceEntriesForNode(position, node, program, sourceFilesToSearch, cancellationToken);
return referenceEntries && convertReferencedSymbols(referenceEntries);
}
function convertReferencedSymbols(referenceEntries: ReadonlyArray<FindAllReferences.Entry>): DocumentHighlights[] {
const fileNameToDocumentHighlights = createMap<HighlightSpan[]>();
for (const entry of referenceEntries) {
const { fileName, span } = FindAllReferences.toHighlightSpan(entry);
let highlightSpans = fileNameToDocumentHighlights.get(fileName);
if (!highlightSpans) {
fileNameToDocumentHighlights.set(fileName, highlightSpans = []);
}
highlightSpans.push(span);
}
return arrayFrom(fileNameToDocumentHighlights.entries(), ([fileName, highlightSpans ]) => ({ fileName, highlightSpans }));
if (!referenceEntries) return undefined;
const map = arrayToMultiMap(referenceEntries.map(FindAllReferences.toHighlightSpan), e => e.fileName, e => e.span);
return arrayFrom(map.entries(), ([fileName, highlightSpans]) => ({ fileName, highlightSpans }));
}
function getSyntacticDocumentHighlights(node: Node, sourceFile: SourceFile): DocumentHighlights[] {
+42 -71
View File
@@ -330,17 +330,15 @@ namespace ts.FindAllReferences.Core {
}
// Labels
if (isLabelName(node)) {
if (isJumpStatementTarget(node)) {
const labelDefinition = getTargetLabel((<BreakOrContinueStatement>node.parent), (<Identifier>node).text);
// if we have a label definition, look within its statement for references, if not, then
// the label is undefined and we have no results..
return labelDefinition && getLabelReferencesInNode(labelDefinition.parent, labelDefinition);
}
else {
// it is a label definition and not a target, search within the parent labeledStatement
return getLabelReferencesInNode(node.parent, <Identifier>node);
}
if (isJumpStatementTarget(node)) {
const labelDefinition = getTargetLabel(node.parent, node.text);
// if we have a label definition, look within its statement for references, if not, then
// the label is undefined and we have no results..
return labelDefinition && getLabelReferencesInNode(labelDefinition.parent, labelDefinition);
}
else if (isLabelOfLabeledStatement(node)) {
// it is a label definition and not a target, search within the parent labeledStatement
return getLabelReferencesInNode(node.parent, node);
}
if (isThis(node)) {
@@ -709,6 +707,16 @@ namespace ts.FindAllReferences.Core {
return exposedByParent ? scope.getSourceFile() : scope;
}
/** Used as a quick check for whether a symbol is used at all in a file (besides its definition). */
export function isSymbolReferencedInFile(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile) {
const symbol = checker.getSymbolAtLocation(definition);
if (!symbol) return true; // Be lenient with invalid code.
return getPossibleSymbolReferencePositions(sourceFile, symbol.name).some(position => {
const token = tryCast(getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true), isIdentifier);
return token && token !== definition && token.escapedText === definition.escapedText && checker.getSymbolAtLocation(token) === symbol;
});
}
function getPossibleSymbolReferencePositions(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile): ReadonlyArray<number> {
const positions: number[] = [];
@@ -745,18 +753,13 @@ namespace ts.FindAllReferences.Core {
}
function getLabelReferencesInNode(container: Node, targetLabel: Identifier): SymbolAndEntries[] {
const references: Entry[] = [];
const sourceFile = container.getSourceFile();
const labelName = targetLabel.text;
const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, labelName, container);
for (const position of possiblePositions) {
const references = mapDefined(getPossibleSymbolReferencePositions(sourceFile, labelName, container), position => {
const node = getTouchingWord(sourceFile, position, /*includeJsDocComment*/ false);
// Only pick labels that are either the target label, or have a target that is the target label
if (node && (node === targetLabel || (isJumpStatementTarget(node) && getTargetLabel(node, labelName) === targetLabel))) {
references.push(nodeEntry(node));
}
}
return node && (node === targetLabel || (isJumpStatementTarget(node) && getTargetLabel(node, labelName) === targetLabel)) ? nodeEntry(node) : undefined;
});
return [{ definition: { type: "label", node: targetLabel }, references }];
}
@@ -782,24 +785,16 @@ namespace ts.FindAllReferences.Core {
}
function getAllReferencesForKeyword(sourceFiles: ReadonlyArray<SourceFile>, keywordKind: ts.SyntaxKind, cancellationToken: CancellationToken): SymbolAndEntries[] {
const references: NodeEntry[] = [];
for (const sourceFile of sourceFiles) {
const references = flatMap(sourceFiles, sourceFile => {
cancellationToken.throwIfCancellationRequested();
addReferencesForKeywordInFile(sourceFile, keywordKind, tokenToString(keywordKind), references);
}
return mapDefined(getPossibleSymbolReferencePositions(sourceFile, tokenToString(keywordKind), sourceFile), position => {
const referenceLocation = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true);
return referenceLocation.kind === keywordKind ? nodeEntry(referenceLocation) : undefined;
});
});
return references.length ? [{ definition: { type: "keyword", node: references[0].node }, references }] : undefined;
}
function addReferencesForKeywordInFile(sourceFile: SourceFile, kind: SyntaxKind, searchText: string, references: Push<NodeEntry>): void {
const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText, sourceFile);
for (const position of possiblePositions) {
const referenceLocation = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true);
if (referenceLocation.kind === kind) {
references.push(nodeEntry(referenceLocation));
}
}
}
function getReferencesInSourceFile(sourceFile: ts.SourceFile, search: Search, state: State): void {
state.cancellationToken.throwIfCancellationRequested();
return getReferencesInContainer(sourceFile, sourceFile, search, state);
@@ -1290,15 +1285,11 @@ namespace ts.FindAllReferences.Core {
return undefined;
}
const references: Entry[] = [];
const sourceFile = searchSpaceNode.getSourceFile();
const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "super", searchSpaceNode);
for (const position of possiblePositions) {
const references = mapDefined(getPossibleSymbolReferencePositions(sourceFile, "super", searchSpaceNode), position => {
const node = getTouchingWord(sourceFile, position, /*includeJsDocComment*/ false);
if (!node || node.kind !== SyntaxKind.SuperKeyword) {
continue;
return;
}
const container = getSuperContainer(node, /*stopOnFunctions*/ false);
@@ -1306,10 +1297,8 @@ namespace ts.FindAllReferences.Core {
// If we have a 'super' container, we must have an enclosing class.
// Now make sure the owning class is the same as the search-space
// and has the same static qualifier as the original 'super's owner.
if (container && (ModifierFlags.Static & getModifierFlags(container)) === staticFlag && container.parent.symbol === searchSpaceNode.symbol) {
references.push(nodeEntry(node));
}
}
return container && (ModifierFlags.Static & getModifierFlags(container)) === staticFlag && container.parent.symbol === searchSpaceNode.symbol ? nodeEntry(node) : undefined;
});
return [{ definition: { type: "symbol", symbol: searchSpaceNode.symbol, node: superKeyword }, references }];
}
@@ -1350,19 +1339,10 @@ namespace ts.FindAllReferences.Core {
}
const references: Entry[] = [];
let possiblePositions: ReadonlyArray<number>;
if (searchSpaceNode.kind === SyntaxKind.SourceFile) {
forEach(sourceFiles, sourceFile => {
cancellationToken.throwIfCancellationRequested();
possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "this");
getThisReferencesInFile(sourceFile, sourceFile, possiblePositions, staticFlag, references);
});
}
else {
const sourceFile = searchSpaceNode.getSourceFile();
possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "this", searchSpaceNode);
getThisReferencesInFile(sourceFile, searchSpaceNode, possiblePositions, staticFlag, references);
for (const sourceFile of searchSpaceNode.kind === SyntaxKind.SourceFile ? sourceFiles : [searchSpaceNode.getSourceFile()]) {
cancellationToken.throwIfCancellationRequested();
const positions = getPossibleSymbolReferencePositions(sourceFile, "this", isSourceFile(searchSpaceNode) ? sourceFile : searchSpaceNode);
getThisReferencesInFile(sourceFile, searchSpaceNode.kind === SyntaxKind.SourceFile ? sourceFile : searchSpaceNode, positions, staticFlag, references);
}
return [{
@@ -1411,27 +1391,18 @@ namespace ts.FindAllReferences.Core {
}
function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: ReadonlyArray<SourceFile>, cancellationToken: CancellationToken): SymbolAndEntries[] {
const references: NodeEntry[] = [];
for (const sourceFile of sourceFiles) {
const references = flatMap(sourceFiles, sourceFile => {
cancellationToken.throwIfCancellationRequested();
const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, node.text);
getReferencesForStringLiteralInFile(sourceFile, node.text, possiblePositions, references);
}
return mapDefined(getPossibleSymbolReferencePositions(sourceFile, node.text), position => {
const ref = tryCast(getTouchingWord(sourceFile, position, /*includeJsDocComment*/ false), isStringLiteral);
return ref && ref.text === node.text ? nodeEntry(ref, /*isInString*/ true) : undefined;
});
});
return [{
definition: { type: "string", node },
references
}];
function getReferencesForStringLiteralInFile(sourceFile: SourceFile, searchText: string, possiblePositions: ReadonlyArray<number>, references: Push<NodeEntry>): void {
for (const position of possiblePositions) {
const node = getTouchingWord(sourceFile, position, /*includeJsDocComment*/ false);
if (node && node.kind === SyntaxKind.StringLiteral && (node as StringLiteral).text === searchText) {
references.push(nodeEntry(node, /*isInString*/ true));
}
}
}
}
// For certain symbol kinds, we need to include other symbols in the search set.
+2 -3
View File
@@ -13,9 +13,8 @@ namespace ts.GoToDefinition {
// Labels
if (isJumpStatementTarget(node)) {
const labelName = (<Identifier>node).text;
const label = getTargetLabel((<BreakOrContinueStatement>node.parent), labelName);
return label ? [createDefinitionInfoFromName(label, ScriptElementKind.label, labelName, /*containerName*/ undefined)] : undefined;
const label = getTargetLabel(node.parent, node.text);
return label ? [createDefinitionInfoFromName(label, ScriptElementKind.label, node.text, /*containerName*/ undefined)] : undefined;
}
const typeChecker = program.getTypeChecker();
+5 -3
View File
@@ -109,7 +109,9 @@ namespace ts.FindAllReferences {
}
else if (isDefaultImport(direct)) {
const sourceFileLike = getSourceFileLikeForImportDeclaration(direct);
addIndirectUser(sourceFileLike); // Add a check for indirect uses to handle synthetic default imports
if (!isAvailableThroughGlobal) {
addIndirectUser(sourceFileLike); // Add a check for indirect uses to handle synthetic default imports
}
directImports.push(direct);
}
else {
@@ -651,8 +653,8 @@ namespace ts.FindAllReferences {
if (parent.kind === SyntaxKind.SourceFile) {
return parent as SourceFile;
}
Debug.assert(parent.kind === SyntaxKind.ModuleBlock && isAmbientModuleDeclaration(parent.parent));
return parent.parent as AmbientModuleDeclaration;
Debug.assert(parent.kind === SyntaxKind.ModuleBlock);
return cast(parent.parent, isAmbientModuleDeclaration);
}
function isAmbientModuleDeclaration(node: Node): node is AmbientModuleDeclaration {
+16 -24
View File
@@ -89,45 +89,37 @@ namespace ts.NavigateTo {
}
function tryAddSingleDeclarationName(declaration: Declaration, containers: string[]): boolean {
if (declaration) {
const name = getNameOfDeclaration(declaration);
if (name) {
const text = getTextOfIdentifierOrLiteral(name as (Identifier | LiteralExpression));
if (text !== undefined) {
containers.unshift(text);
}
else if (name.kind === SyntaxKind.ComputedPropertyName) {
return tryAddComputedPropertyName(name.expression, containers, /*includeLastPortion*/ true);
}
else {
// Don't know how to add this.
return false;
}
}
const name = getNameOfDeclaration(declaration);
if (name && isPropertyNameLiteral(name)) {
containers.unshift(getTextOfIdentifierOrLiteral(name));
return true;
}
else if (name && name.kind === SyntaxKind.ComputedPropertyName) {
return tryAddComputedPropertyName(name.expression, containers, /*includeLastPortion*/ true);
}
else {
// Don't know how to add this.
return false;
}
return true;
}
// Only added the names of computed properties if they're simple dotted expressions, like:
//
// [X.Y.Z]() { }
function tryAddComputedPropertyName(expression: Expression, containers: string[], includeLastPortion: boolean): boolean {
const text = getTextOfIdentifierOrLiteral(expression as LiteralExpression);
if (text !== undefined) {
if (isPropertyNameLiteral(expression)) {
const text = getTextOfIdentifierOrLiteral(expression);
if (includeLastPortion) {
containers.unshift(text);
}
return true;
}
if (expression.kind === SyntaxKind.PropertyAccessExpression) {
const propertyAccess = <PropertyAccessExpression>expression;
if (isPropertyAccessExpression(expression)) {
if (includeLastPortion) {
containers.unshift(propertyAccess.name.text);
containers.unshift(expression.name.text);
}
return tryAddComputedPropertyName(propertyAccess.expression, containers, /*includeLastPortion*/ true);
return tryAddComputedPropertyName(expression.expression, containers, /*includeLastPortion*/ true);
}
return false;
+101 -36
View File
@@ -1,9 +1,17 @@
/* @internal */
namespace ts.OrganizeImports {
/**
* Organize imports by:
* 1) Removing unused imports
* 2) Coalescing imports from the same module
* 3) Sorting imports
*/
export function organizeImports(
sourceFile: SourceFile,
formatContext: formatting.FormatContext,
host: LanguageServiceHost) {
host: LanguageServiceHost,
program: Program) {
// TODO (https://github.com/Microsoft/TypeScript/issues/10020): sort *within* ambient modules (find using isAmbientModule)
@@ -21,7 +29,7 @@ namespace ts.OrganizeImports {
const newImportDecls = flatMap(sortedImportGroups, importGroup =>
getExternalModuleName(importGroup[0].moduleSpecifier)
? coalesceImports(removeUnusedImports(importGroup))
? coalesceImports(removeUnusedImports(importGroup, sourceFile, program))
: importGroup);
const changeTracker = textChanges.ChangeTracker.fromContext({ host, formatContext });
@@ -47,8 +55,58 @@ namespace ts.OrganizeImports {
return changeTracker.getChanges();
}
function removeUnusedImports(oldImports: ReadonlyArray<ImportDeclaration>) {
return oldImports; // TODO (https://github.com/Microsoft/TypeScript/issues/10020)
function removeUnusedImports(oldImports: ReadonlyArray<ImportDeclaration>, sourceFile: SourceFile, program: Program) {
const typeChecker = program.getTypeChecker();
const jsxNamespace = typeChecker.getJsxNamespace();
const jsxContext = sourceFile.languageVariant === LanguageVariant.JSX && program.getCompilerOptions().jsx;
const usedImports: ImportDeclaration[] = [];
for (const importDecl of oldImports) {
const {importClause} = importDecl;
if (!importClause) {
// Imports without import clauses are assumed to be included for their side effects and are not removed.
usedImports.push(importDecl);
continue;
}
let { name, namedBindings } = importClause;
// Default import
if (name && !isDeclarationUsed(name)) {
name = undefined;
}
if (namedBindings) {
if (isNamespaceImport(namedBindings)) {
// Namespace import
if (!isDeclarationUsed(namedBindings.name)) {
namedBindings = undefined;
}
}
else {
// List of named imports
const newElements = namedBindings.elements.filter(e => isDeclarationUsed(e.propertyName || e.name));
if (newElements.length < namedBindings.elements.length) {
namedBindings = newElements.length
? updateNamedImports(namedBindings, newElements)
: undefined;
}
}
}
if (name || namedBindings) {
usedImports.push(updateImportDeclarationAndClause(importDecl, name, namedBindings));
}
}
return usedImports;
function isDeclarationUsed(identifier: Identifier) {
// The JSX factory symbol is always used.
return jsxContext && (identifier.text === jsxNamespace) || FindAllReferences.Core.isSymbolReferencedInFile(identifier, typeChecker, sourceFile);
}
}
function getExternalModuleName(specifier: Expression) {
@@ -66,7 +124,7 @@ namespace ts.OrganizeImports {
return importGroup;
}
const { importWithoutClause, defaultImports, namespaceImports, namedImports } = getImportParts(importGroup);
const { importWithoutClause, defaultImports, namespaceImports, namedImports } = getCategorizedImports(importGroup);
const coalescedImports: ImportDeclaration[] = [];
@@ -78,19 +136,20 @@ namespace ts.OrganizeImports {
// produce two import declarations in this special case.
if (defaultImports.length === 1 && namespaceImports.length === 1 && namedImports.length === 0) {
// Add the namespace import to the existing default ImportDeclaration.
const defaultImportClause = defaultImports[0].parent as ImportClause;
const defaultImport = defaultImports[0];
coalescedImports.push(
updateImportDeclarationAndClause(defaultImportClause, defaultImportClause.name, namespaceImports[0]));
updateImportDeclarationAndClause(defaultImport, defaultImport.importClause.name, namespaceImports[0].importClause.namedBindings));
return coalescedImports;
}
const sortedNamespaceImports = stableSort(namespaceImports, (n1, n2) => compareIdentifiers(n1.name, n2.name));
const sortedNamespaceImports = stableSort(namespaceImports, (i1, i2) =>
compareIdentifiers((i1.importClause.namedBindings as NamespaceImport).name, (i2.importClause.namedBindings as NamespaceImport).name));
for (const namespaceImport of sortedNamespaceImports) {
// Drop the name, if any
coalescedImports.push(
updateImportDeclarationAndClause(namespaceImport.parent, /*name*/ undefined, namespaceImport));
updateImportDeclarationAndClause(namespaceImport, /*name*/ undefined, namespaceImport.importClause.namedBindings));
}
if (defaultImports.length === 0 && namedImports.length === 0) {
@@ -100,41 +159,48 @@ namespace ts.OrganizeImports {
let newDefaultImport: Identifier | undefined;
const newImportSpecifiers: ImportSpecifier[] = [];
if (defaultImports.length === 1) {
newDefaultImport = defaultImports[0];
newDefaultImport = defaultImports[0].importClause.name;
}
else {
for (const defaultImport of defaultImports) {
newImportSpecifiers.push(
createImportSpecifier(createIdentifier("default"), defaultImport));
createImportSpecifier(createIdentifier("default"), defaultImport.importClause.name));
}
}
newImportSpecifiers.push(...flatMap(namedImports, n => n.elements));
newImportSpecifiers.push(...flatMap(namedImports, i => (i.importClause.namedBindings as NamedImports).elements));
const sortedImportSpecifiers = stableSort(newImportSpecifiers, (s1, s2) =>
compareIdentifiers(s1.propertyName || s1.name, s2.propertyName || s2.name) ||
compareIdentifiers(s1.name, s2.name));
const importClause = defaultImports.length > 0
? defaultImports[0].parent as ImportClause
: namedImports[0].parent;
const importDecl = defaultImports.length > 0
? defaultImports[0]
: namedImports[0];
const newNamedImports = sortedImportSpecifiers.length === 0
? undefined
: namedImports.length === 0
? createNamedImports(sortedImportSpecifiers)
: updateNamedImports(namedImports[0], sortedImportSpecifiers);
: updateNamedImports(namedImports[0].importClause.namedBindings as NamedImports, sortedImportSpecifiers);
coalescedImports.push(
updateImportDeclarationAndClause(importClause, newDefaultImport, newNamedImports));
updateImportDeclarationAndClause(importDecl, newDefaultImport, newNamedImports));
return coalescedImports;
function getImportParts(importGroup: ReadonlyArray<ImportDeclaration>) {
/*
* Returns entire import declarations because they may already have been rewritten and
* may lack parent pointers. The desired parts can easily be recovered based on the
* categorization.
*
* NB: There may be overlap between `defaultImports` and `namespaceImports`/`namedImports`.
*/
function getCategorizedImports(importGroup: ReadonlyArray<ImportDeclaration>) {
let importWithoutClause: ImportDeclaration | undefined;
const defaultImports: Identifier[] = [];
const namespaceImports: NamespaceImport[] = [];
const namedImports: NamedImports[] = [];
const defaultImports: ImportDeclaration[] = [];
const namespaceImports: ImportDeclaration[] = [];
const namedImports: ImportDeclaration[] = [];
for (const importDeclaration of importGroup) {
if (importDeclaration.importClause === undefined) {
@@ -147,15 +213,15 @@ namespace ts.OrganizeImports {
const { name, namedBindings } = importDeclaration.importClause;
if (name) {
defaultImports.push(name);
defaultImports.push(importDeclaration);
}
if (namedBindings) {
if (isNamespaceImport(namedBindings)) {
namespaceImports.push(namedBindings);
namespaceImports.push(importDeclaration);
}
else {
namedImports.push(namedBindings);
namedImports.push(importDeclaration);
}
}
}
@@ -171,20 +237,19 @@ namespace ts.OrganizeImports {
function compareIdentifiers(s1: Identifier, s2: Identifier) {
return compareStringsCaseSensitive(s1.text, s2.text);
}
}
function updateImportDeclarationAndClause(
importClause: ImportClause,
name: Identifier | undefined,
namedBindings: NamedImportBindings | undefined) {
function updateImportDeclarationAndClause(
importDeclaration: ImportDeclaration,
name: Identifier | undefined,
namedBindings: NamedImportBindings | undefined) {
const importDeclaration = importClause.parent;
return updateImportDeclaration(
importDeclaration,
importDeclaration.decorators,
importDeclaration.modifiers,
updateImportClause(importClause, name, namedBindings),
importDeclaration.moduleSpecifier);
}
return updateImportDeclaration(
importDeclaration,
importDeclaration.decorators,
importDeclaration.modifiers,
updateImportClause(importDeclaration.importClause, name, namedBindings),
importDeclaration.moduleSpecifier);
}
/* internal */ // Exported for testing
+5 -13
View File
@@ -24,25 +24,19 @@ namespace ts.Rename {
// Only allow a symbol to be renamed if it actually has at least one declaration.
if (symbol) {
const declarations = symbol.getDeclarations();
const { declarations } = symbol;
if (declarations && declarations.length > 0) {
// Disallow rename for elements that are defined in the standard TypeScript library.
if (some(declarations, isDefinedInLibraryFile)) {
if (declarations.some(isDefinedInLibraryFile)) {
return getRenameInfoError(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library);
}
// Cannot rename `default` as in `import { default as foo } from "./someModule";
if (node.kind === SyntaxKind.Identifier &&
(node as Identifier).originalKeywordKind === SyntaxKind.DefaultKeyword &&
symbol.parent.flags & ts.SymbolFlags.Module) {
if (isIdentifier(node) && node.originalKeywordKind === SyntaxKind.DefaultKeyword && symbol.parent.flags & SymbolFlags.Module) {
return undefined;
}
const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, node);
if (!kind) {
return undefined;
}
const specifierName = (isImportOrExportSpecifierName(node) || isStringOrNumericLiteral(node) && node.parent.kind === SyntaxKind.ComputedPropertyName)
? stripQuotes(getTextOfIdentifierOrLiteral(node))
: undefined;
@@ -51,13 +45,11 @@ namespace ts.Rename {
return getRenameInfoSuccess(displayName, fullDisplayName, kind, SymbolDisplay.getSymbolModifiers(symbol), node, sourceFile);
}
}
else if (node.kind === SyntaxKind.StringLiteral) {
else if (isStringLiteral(node)) {
if (isDefinedInLibraryFile(node)) {
return getRenameInfoError(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library);
}
const displayName = stripQuotes((node as StringLiteral).text);
return getRenameInfoSuccess(displayName, displayName, ScriptElementKind.variableElement, ScriptElementKindModifier.none, node, sourceFile);
return getRenameInfoSuccess(node.text, node.text, ScriptElementKind.variableElement, ScriptElementKindModifier.none, node, sourceFile);
}
}
+26 -43
View File
@@ -721,23 +721,8 @@ namespace ts {
function getDeclarationName(declaration: Declaration) {
const name = getNameOfDeclaration(declaration);
if (name) {
const result = getTextOfIdentifierOrLiteral(name as (Identifier | LiteralExpression));
if (result !== undefined) {
return result;
}
if (name.kind === SyntaxKind.ComputedPropertyName) {
const expr = name.expression;
if (expr.kind === SyntaxKind.PropertyAccessExpression) {
return (<PropertyAccessExpression>expr).name.text;
}
return getTextOfIdentifierOrLiteral(expr as (Identifier | LiteralExpression));
}
}
return undefined;
return name && (isPropertyNameLiteral(name) ? getTextOfIdentifierOrLiteral(name) :
name.kind === SyntaxKind.ComputedPropertyName && isPropertyAccessExpression(name.expression) ? name.expression.name.text : undefined);
}
function visit(node: Node): void {
@@ -1482,10 +1467,7 @@ namespace ts {
const sourceFile = getValidSourceFile(fileName);
const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true);
if (node === sourceFile) {
return undefined;
}
if (isLabelName(node)) {
// Avoid giving quickInfo for the sourceFile as a whole.
return undefined;
}
@@ -1496,6 +1478,11 @@ namespace ts {
// Try getting just type at this position and show
switch (node.kind) {
case SyntaxKind.Identifier:
if (isLabelName(node)) {
// Type here will be 'any', avoid displaying this.
return undefined;
}
// falls through
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.QualifiedName:
case SyntaxKind.ThisKeyword:
@@ -1503,29 +1490,27 @@ namespace ts {
case SyntaxKind.SuperKeyword:
// For the identifiers/this/super etc get the type at position
const type = typeChecker.getTypeAtLocation(node);
if (type) {
return {
kind: ScriptElementKind.unknown,
kindModifiers: ScriptElementKindModifier.none,
textSpan: createTextSpan(node.getStart(), node.getWidth()),
displayParts: typeToDisplayParts(typeChecker, type, getContainerNode(node)),
documentation: type.symbol ? type.symbol.getDocumentationComment(typeChecker) : undefined,
tags: type.symbol ? type.symbol.getJsDocTags() : undefined
};
}
return type && {
kind: ScriptElementKind.unknown,
kindModifiers: ScriptElementKindModifier.none,
textSpan: createTextSpanFromNode(node, sourceFile),
displayParts: typeToDisplayParts(typeChecker, type, getContainerNode(node)),
documentation: type.symbol ? type.symbol.getDocumentationComment(typeChecker) : undefined,
tags: type.symbol ? type.symbol.getJsDocTags() : undefined
};
}
return undefined;
}
const displayPartsDocumentationsAndKind = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, getContainerNode(node), node);
const { symbolKind, displayParts, documentation, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, getContainerNode(node), node);
return {
kind: displayPartsDocumentationsAndKind.symbolKind,
kind: symbolKind,
kindModifiers: SymbolDisplay.getSymbolModifiers(symbol),
textSpan: createTextSpan(node.getStart(), node.getWidth()),
displayParts: displayPartsDocumentationsAndKind.displayParts,
documentation: displayPartsDocumentationsAndKind.documentation,
tags: displayPartsDocumentationsAndKind.tags
textSpan: createTextSpanFromNode(node, sourceFile),
displayParts,
documentation,
tags,
};
}
@@ -1534,11 +1519,9 @@ namespace ts {
&& isPropertyAssignment(node.parent)
&& node.parent.name === node) {
const type = checker.getContextualType(node.parent.parent);
if (type) {
const property = checker.getPropertyOfType(type, getTextOfIdentifierOrLiteral(node));
if (property) {
return property;
}
const property = type && checker.getPropertyOfType(type, getTextOfIdentifierOrLiteral(node));
if (property) {
return property;
}
}
return checker.getSymbolAtLocation(node);
@@ -1855,7 +1838,7 @@ namespace ts {
const sourceFile = getValidSourceFile(scope.fileName);
const formatContext = formatting.getFormatContext(formatOptions);
return OrganizeImports.organizeImports(sourceFile, formatContext, host);
return OrganizeImports.organizeImports(sourceFile, formatContext, host, program);
}
function applyCodeActionCommand(action: CodeActionCommand): Promise<ApplyCodeActionCommandResult>;
+1 -1
View File
@@ -727,7 +727,7 @@ namespace ts {
}
export interface CompletionInfo {
/** Not true for all glboal completions. This will be true if the enclosing scope matches a few syntax kinds. See `isGlobalCompletionScope`. */
/** Not true for all glboal completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */
isGlobalCompletion: boolean;
isMemberCompletion: boolean;
+10 -27
View File
@@ -92,7 +92,7 @@ namespace ts {
return SemanticMeaning.All;
}
else if (isInRightSideOfInternalImportEqualsDeclaration(node)) {
return getMeaningFromRightHandSideOfImportEquals(node);
return getMeaningFromRightHandSideOfImportEquals(node as Identifier);
}
else if (isDeclarationName(node)) {
return getMeaningFromDeclaration(node.parent);
@@ -112,19 +112,12 @@ namespace ts {
}
}
function getMeaningFromRightHandSideOfImportEquals(node: Node) {
Debug.assert(node.kind === SyntaxKind.Identifier);
function getMeaningFromRightHandSideOfImportEquals(node: Node): SemanticMeaning {
// import a = |b|; // Namespace
// import a = |b.c|; // Value, type, namespace
// import a = |b.c|.d; // Namespace
if (node.parent.kind === SyntaxKind.QualifiedName &&
(<QualifiedName>node.parent).right === node &&
node.parent.parent.kind === SyntaxKind.ImportEqualsDeclaration) {
return SemanticMeaning.Value | SemanticMeaning.Type | SemanticMeaning.Namespace;
}
return SemanticMeaning.Namespace;
const name = node.kind === SyntaxKind.QualifiedName ? node : isQualifiedName(node.parent) && node.parent.right === node ? node.parent : undefined;
return name && name.parent.kind === SyntaxKind.ImportEqualsDeclaration ? SemanticMeaning.All : SemanticMeaning.Namespace;
}
export function isInRightSideOfInternalImportEqualsDeclaration(node: Node) {
@@ -221,16 +214,12 @@ namespace ts {
return undefined;
}
export function isJumpStatementTarget(node: Node): boolean {
return node.kind === SyntaxKind.Identifier &&
(node.parent.kind === SyntaxKind.BreakStatement || node.parent.kind === SyntaxKind.ContinueStatement) &&
(<BreakOrContinueStatement>node.parent).label === node;
}
export function isJumpStatementTarget(node: Node): node is Identifier & { parent: BreakOrContinueStatement } {
return node.kind === SyntaxKind.Identifier && isBreakOrContinueStatement(node.parent) && node.parent.label === node;
}
function isLabelOfLabeledStatement(node: Node): boolean {
return node.kind === SyntaxKind.Identifier &&
node.parent.kind === SyntaxKind.LabeledStatement &&
(<LabeledStatement>node.parent).label === node;
export function isLabelOfLabeledStatement(node: Node): node is Identifier {
return node.kind === SyntaxKind.Identifier && isLabeledStatement(node.parent) && node.parent.label === node;
}
export function isLabelName(node: Node): boolean {
@@ -632,13 +621,7 @@ namespace ts {
// be parented by the container of the SyntaxList, not the SyntaxList itself.
// In order to find the list item index, we first need to locate SyntaxList itself and then search
// for the position of the relevant node (or comma).
const syntaxList = forEach(node.parent.getChildren(), c => {
// find syntax list that covers the span of the node
if (isSyntaxList(c) && c.pos <= node.pos && c.end >= node.end) {
return c;
}
});
const syntaxList = find(node.parent.getChildren(), (c): c is SyntaxList => isSyntaxList(c) && rangeContainsRange(c, node));
// Either we didn't find an appropriate list, or the list must contain us.
Debug.assert(!syntaxList || contains(syntaxList.getChildren(), node));
return syntaxList;