mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge branch 'master' into add-codefix-cannot-find-name-in-for-loop
This commit is contained in:
commit
bd9f39dbcc
1356 files changed
+59797
-13031
No files matched your search
@@ -15,11 +15,11 @@ namespace ts.codefix {
|
||||
function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number) {
|
||||
const token = getTokenAtPosition(sourceFile, pos);
|
||||
if (!isIdentifier(token)) {
|
||||
return Debug.fail("add-name-to-nameless-parameter operates on identifiers, but got a " + formatSyntaxKind(token.kind));
|
||||
return Debug.fail("add-name-to-nameless-parameter operates on identifiers, but got a " + Debug.formatSyntaxKind(token.kind));
|
||||
}
|
||||
const param = token.parent;
|
||||
if (!isParameter(param)) {
|
||||
return Debug.fail("Tried to add a parameter name to a non-parameter: " + formatSyntaxKind(token.kind));
|
||||
return Debug.fail("Tried to add a parameter name to a non-parameter: " + Debug.formatSyntaxKind(token.kind));
|
||||
}
|
||||
const i = param.parent.parameters.indexOf(param);
|
||||
Debug.assert(!param.type, "Tried to add a parameter name to a parameter that already had one.");
|
||||
|
||||
@@ -31,16 +31,16 @@ namespace ts.codefix {
|
||||
|
||||
registerCodeFix({
|
||||
errorCodes: [
|
||||
Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures.code,
|
||||
Diagnostics.Cannot_use_new_with_an_expression_whose_type_lacks_a_call_or_construct_signature.code,
|
||||
Diagnostics.This_expression_is_not_callable.code,
|
||||
Diagnostics.This_expression_is_not_constructable.code,
|
||||
],
|
||||
getCodeActions: getActionsForUsageOfInvalidImport
|
||||
});
|
||||
|
||||
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), a => a.kind === targetKind && a.getStart() === context.span.start && a.getEnd() === (context.span.start + context.span.length)) as CallExpression | NewExpression;
|
||||
const targetKind = Diagnostics.This_expression_is_not_callable.code === context.errorCode ? SyntaxKind.CallExpression : SyntaxKind.NewExpression;
|
||||
const node = findAncestor(getTokenAtPosition(sourceFile, context.span.start), a => a.kind === targetKind) as CallExpression | NewExpression;
|
||||
if (!node) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -42,6 +42,9 @@ namespace ts.codefix {
|
||||
|
||||
// Property declarations
|
||||
Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code,
|
||||
|
||||
// Function expressions and declarations
|
||||
Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation.code,
|
||||
];
|
||||
registerCodeFix({
|
||||
errorCodes,
|
||||
@@ -73,6 +76,8 @@ namespace ts.codefix {
|
||||
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code:
|
||||
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage.code:
|
||||
return Diagnostics.Infer_parameter_types_from_usage;
|
||||
case Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation.code:
|
||||
return Diagnostics.Infer_this_type_of_0_from_usage;
|
||||
default:
|
||||
return Diagnostics.Infer_type_of_0_from_usage;
|
||||
}
|
||||
@@ -176,6 +181,14 @@ namespace ts.codefix {
|
||||
}
|
||||
return undefined;
|
||||
|
||||
// Function 'this'
|
||||
case Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation.code:
|
||||
if (textChanges.isThisTypeAnnotatable(containingFunction) && markSeen(containingFunction)) {
|
||||
annotateThis(changes, sourceFile, containingFunction, program, host, cancellationToken);
|
||||
return containingFunction;
|
||||
}
|
||||
return undefined;
|
||||
|
||||
default:
|
||||
return Debug.fail(String(errorCode));
|
||||
}
|
||||
@@ -191,7 +204,9 @@ namespace ts.codefix {
|
||||
if (!isIdentifier(parameterDeclaration.name)) {
|
||||
return;
|
||||
}
|
||||
const parameterInferences = inferTypeForParametersFromUsage(containingFunction, sourceFile, program, cancellationToken) ||
|
||||
|
||||
const references = inferFunctionReferencesFromUsage(containingFunction, sourceFile, program, cancellationToken);
|
||||
const parameterInferences = InferFromReference.inferTypeForParametersFromReferences(references, containingFunction, program, cancellationToken) ||
|
||||
containingFunction.parameters.map<ParameterInference>(p => ({
|
||||
declaration: p,
|
||||
type: isIdentifier(p.name) ? inferTypeForVariableFromUsage(p.name, program, cancellationToken) : program.getTypeChecker().getAnyType()
|
||||
@@ -213,6 +228,36 @@ namespace ts.codefix {
|
||||
}
|
||||
}
|
||||
|
||||
function annotateThis(changes: textChanges.ChangeTracker, sourceFile: SourceFile, containingFunction: textChanges.ThisTypeAnnotatable, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken) {
|
||||
const references = inferFunctionReferencesFromUsage(containingFunction, sourceFile, program, cancellationToken);
|
||||
if (!references) {
|
||||
return;
|
||||
}
|
||||
|
||||
const thisInference = InferFromReference.inferTypeForThisFromReferences(references, program, cancellationToken);
|
||||
if (!thisInference) {
|
||||
return;
|
||||
}
|
||||
|
||||
const typeNode = getTypeNodeIfAccessible(thisInference, containingFunction, program, host);
|
||||
if (!typeNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isInJSFile(containingFunction)) {
|
||||
annotateJSDocThis(changes, sourceFile, containingFunction, typeNode);
|
||||
}
|
||||
else {
|
||||
changes.tryInsertThisTypeAnnotation(sourceFile, containingFunction, typeNode);
|
||||
}
|
||||
}
|
||||
|
||||
function annotateJSDocThis(changes: textChanges.ChangeTracker, sourceFile: SourceFile, containingFunction: FunctionLike, typeNode: TypeNode) {
|
||||
addJSDocTags(changes, sourceFile, containingFunction, [
|
||||
createJSDocThisTag(createJSDocTypeExpression(typeNode)),
|
||||
]);
|
||||
}
|
||||
|
||||
function annotateSetAccessor(changes: textChanges.ChangeTracker, sourceFile: SourceFile, setAccessorDeclaration: SetAccessorDeclaration, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken): void {
|
||||
const param = firstOrUndefined(setAccessorDeclaration.parameters);
|
||||
if (param && isIdentifier(setAccessorDeclaration.name) && isIdentifier(param.name)) {
|
||||
@@ -317,7 +362,7 @@ namespace ts.codefix {
|
||||
return InferFromReference.unifyFromContext(types, checker);
|
||||
}
|
||||
|
||||
function inferTypeForParametersFromUsage(containingFunction: FunctionLike, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): ParameterInference[] | undefined {
|
||||
function inferFunctionReferencesFromUsage(containingFunction: FunctionLike, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): ReadonlyArray<Identifier> | undefined {
|
||||
let searchToken;
|
||||
switch (containingFunction.kind) {
|
||||
case SyntaxKind.Constructor:
|
||||
@@ -335,9 +380,12 @@ namespace ts.codefix {
|
||||
searchToken = containingFunction.name;
|
||||
break;
|
||||
}
|
||||
if (searchToken) {
|
||||
return InferFromReference.inferTypeForParametersFromReferences(getReferences(searchToken, program, cancellationToken), containingFunction, program, cancellationToken);
|
||||
|
||||
if (!searchToken) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getReferences(searchToken, program, cancellationToken);
|
||||
}
|
||||
|
||||
interface ParameterInference {
|
||||
@@ -364,6 +412,7 @@ namespace ts.codefix {
|
||||
constructContexts?: CallContext[];
|
||||
numberIndexContext?: UsageContext;
|
||||
stringIndexContext?: UsageContext;
|
||||
candidateThisTypes?: Type[];
|
||||
}
|
||||
|
||||
export function inferTypesFromReferences(references: ReadonlyArray<Identifier>, checker: TypeChecker, cancellationToken: CancellationToken): Type[] {
|
||||
@@ -375,15 +424,12 @@ namespace ts.codefix {
|
||||
return inferFromContext(usageContext, checker);
|
||||
}
|
||||
|
||||
export function inferTypeForParametersFromReferences(references: ReadonlyArray<Identifier>, declaration: FunctionLike, program: Program, cancellationToken: CancellationToken): ParameterInference[] | undefined {
|
||||
const checker = program.getTypeChecker();
|
||||
if (references.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (!declaration.parameters) {
|
||||
export function inferTypeForParametersFromReferences(references: ReadonlyArray<Identifier> | undefined, declaration: FunctionLike, program: Program, cancellationToken: CancellationToken): ParameterInference[] | undefined {
|
||||
if (references === undefined || references.length === 0 || !declaration.parameters) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const checker = program.getTypeChecker();
|
||||
const usageContext: UsageContext = {};
|
||||
for (const reference of references) {
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
@@ -421,6 +467,22 @@ namespace ts.codefix {
|
||||
});
|
||||
}
|
||||
|
||||
export function inferTypeForThisFromReferences(references: ReadonlyArray<Identifier>, program: Program, cancellationToken: CancellationToken) {
|
||||
if (references.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const checker = program.getTypeChecker();
|
||||
const usageContext: UsageContext = {};
|
||||
|
||||
for (const reference of references) {
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
inferTypeFromContext(reference, checker, usageContext);
|
||||
}
|
||||
|
||||
return unifyFromContext(usageContext.candidateThisTypes || emptyArray, checker);
|
||||
}
|
||||
|
||||
function inferTypeFromContext(node: Expression, checker: TypeChecker, usageContext: UsageContext): void {
|
||||
while (isRightSideOfQualifiedNameOrPropertyAccess(node)) {
|
||||
node = <Expression>node.parent;
|
||||
@@ -455,6 +517,13 @@ namespace ts.codefix {
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
inferTypeFromPropertyElementExpressionContext(<ElementAccessExpression>node.parent, node, checker, usageContext);
|
||||
break;
|
||||
case SyntaxKind.PropertyAssignment:
|
||||
case SyntaxKind.ShorthandPropertyAssignment:
|
||||
inferTypeFromPropertyAssignment(<PropertyAssignment | ShorthandPropertyAssignment>node.parent, checker, usageContext);
|
||||
break;
|
||||
case SyntaxKind.PropertyDeclaration:
|
||||
inferTypeFromPropertyDeclaration(<PropertyDeclaration>node.parent, checker, usageContext);
|
||||
break;
|
||||
case SyntaxKind.VariableDeclaration: {
|
||||
const { name, initializer } = node.parent as VariableDeclaration;
|
||||
if (node === name) {
|
||||
@@ -647,6 +716,21 @@ namespace ts.codefix {
|
||||
}
|
||||
}
|
||||
|
||||
function inferTypeFromPropertyAssignment(assignment: PropertyAssignment | ShorthandPropertyAssignment, checker: TypeChecker, usageContext: UsageContext) {
|
||||
const objectLiteral = isShorthandPropertyAssignment(assignment) ?
|
||||
assignment.parent :
|
||||
assignment.parent.parent;
|
||||
const nodeWithRealType = isVariableDeclaration(objectLiteral.parent) ?
|
||||
objectLiteral.parent :
|
||||
objectLiteral;
|
||||
|
||||
addCandidateThisType(usageContext, checker.getTypeAtLocation(nodeWithRealType));
|
||||
}
|
||||
|
||||
function inferTypeFromPropertyDeclaration(declaration: PropertyDeclaration, checker: TypeChecker, usageContext: UsageContext) {
|
||||
addCandidateThisType(usageContext, checker.getTypeAtLocation(declaration.parent));
|
||||
}
|
||||
|
||||
interface Priority {
|
||||
high: (t: Type) => boolean;
|
||||
low: (t: Type) => boolean;
|
||||
@@ -841,6 +925,12 @@ namespace ts.codefix {
|
||||
}
|
||||
}
|
||||
|
||||
function addCandidateThisType(context: UsageContext, type: Type | undefined) {
|
||||
if (type && !(type.flags & TypeFlags.Any) && !(type.flags & TypeFlags.Never)) {
|
||||
(context.candidateThisTypes || (context.candidateThisTypes = [])).push(type);
|
||||
}
|
||||
}
|
||||
|
||||
function hasCallContext(usageContext: UsageContext | undefined): boolean {
|
||||
return !!usageContext && !!usageContext.callContexts;
|
||||
}
|
||||
|
||||
+107
-24
@@ -1,5 +1,12 @@
|
||||
/* @internal */
|
||||
namespace ts.Completions {
|
||||
export enum SortText {
|
||||
LocationPriority = "0",
|
||||
SuggestedClassMembers = "1",
|
||||
GlobalsOrKeywords = "2",
|
||||
AutoImportSuggestions = "3",
|
||||
JavascriptIdentifiers = "4"
|
||||
}
|
||||
export type Log = (message: string) => void;
|
||||
|
||||
const enum SymbolOriginInfoKind { ThisType, SymbolMemberNoExport, SymbolMemberExport, Export }
|
||||
@@ -22,6 +29,8 @@ namespace ts.Completions {
|
||||
*/
|
||||
type SymbolOriginInfoMap = (SymbolOriginInfo | undefined)[];
|
||||
|
||||
type SymbolSortTextMap = (SortText | undefined)[];
|
||||
|
||||
const enum KeywordCompletionFilters {
|
||||
None, // No keywords
|
||||
All, // Every possible keyword (TODO: This is never appropriate)
|
||||
@@ -40,7 +49,9 @@ namespace ts.Completions {
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
|
||||
const contextToken = findPrecedingToken(position, sourceFile);
|
||||
if (triggerCharacter && !isValidTrigger(sourceFile, triggerCharacter, contextToken, position)) return undefined;
|
||||
if (triggerCharacter && !isInString(sourceFile, position, contextToken) && !isValidTrigger(sourceFile, triggerCharacter, contextToken, position)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const stringCompletions = StringCompletions.getStringLiteralCompletions(sourceFile, position, contextToken, typeChecker, compilerOptions, host, log, preferences);
|
||||
if (stringCompletions) {
|
||||
@@ -78,7 +89,21 @@ namespace ts.Completions {
|
||||
}
|
||||
|
||||
function completionInfoFromData(sourceFile: SourceFile, typeChecker: TypeChecker, compilerOptions: CompilerOptions, log: Log, completionData: CompletionData, preferences: UserPreferences): CompletionInfo | undefined {
|
||||
const { symbols, completionKind, isInSnippetScope, isNewIdentifierLocation, location, propertyAccessToConvert, keywordFilters, literals, symbolToOriginInfoMap, recommendedCompletion, isJsxInitializer, insideJsDocTagTypeExpression } = completionData;
|
||||
const {
|
||||
symbols,
|
||||
completionKind,
|
||||
isInSnippetScope,
|
||||
isNewIdentifierLocation,
|
||||
location,
|
||||
propertyAccessToConvert,
|
||||
keywordFilters,
|
||||
literals,
|
||||
symbolToOriginInfoMap,
|
||||
recommendedCompletion,
|
||||
isJsxInitializer,
|
||||
insideJsDocTagTypeExpression,
|
||||
symbolToSortTextMap,
|
||||
} = completionData;
|
||||
|
||||
if (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,
|
||||
@@ -93,7 +118,7 @@ namespace ts.Completions {
|
||||
name: tagName.getFullText(sourceFile) + (hasClosingAngleBracket ? "" : ">"),
|
||||
kind: ScriptElementKind.classElement,
|
||||
kindModifiers: undefined,
|
||||
sortText: "0",
|
||||
sortText: SortText.LocationPriority,
|
||||
};
|
||||
return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: false, entries: [entry] };
|
||||
}
|
||||
@@ -101,7 +126,22 @@ namespace ts.Completions {
|
||||
const entries: CompletionEntry[] = [];
|
||||
|
||||
if (isUncheckedFile(sourceFile, compilerOptions)) {
|
||||
const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries, location, sourceFile, typeChecker, compilerOptions.target!, log, completionKind, preferences, propertyAccessToConvert, isJsxInitializer, recommendedCompletion, symbolToOriginInfoMap);
|
||||
const uniqueNames = getCompletionEntriesFromSymbols(
|
||||
symbols,
|
||||
entries,
|
||||
location,
|
||||
sourceFile,
|
||||
typeChecker,
|
||||
compilerOptions.target!,
|
||||
log,
|
||||
completionKind,
|
||||
preferences,
|
||||
propertyAccessToConvert,
|
||||
isJsxInitializer,
|
||||
recommendedCompletion,
|
||||
symbolToOriginInfoMap,
|
||||
symbolToSortTextMap
|
||||
);
|
||||
getJSCompletionEntries(sourceFile, location!.pos, uniqueNames, compilerOptions.target!, entries); // TODO: GH#18217
|
||||
}
|
||||
else {
|
||||
@@ -109,7 +149,22 @@ namespace ts.Completions {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getCompletionEntriesFromSymbols(symbols, entries, location, sourceFile, typeChecker, compilerOptions.target!, log, completionKind, preferences, propertyAccessToConvert, isJsxInitializer, recommendedCompletion, symbolToOriginInfoMap);
|
||||
getCompletionEntriesFromSymbols(
|
||||
symbols,
|
||||
entries,
|
||||
location,
|
||||
sourceFile,
|
||||
typeChecker,
|
||||
compilerOptions.target!,
|
||||
log,
|
||||
completionKind,
|
||||
preferences,
|
||||
propertyAccessToConvert,
|
||||
isJsxInitializer,
|
||||
recommendedCompletion,
|
||||
symbolToOriginInfoMap,
|
||||
symbolToSortTextMap
|
||||
);
|
||||
}
|
||||
|
||||
if (keywordFilters !== KeywordCompletionFilters.None) {
|
||||
@@ -160,7 +215,7 @@ namespace ts.Completions {
|
||||
name: realName,
|
||||
kind: ScriptElementKind.warning,
|
||||
kindModifiers: "",
|
||||
sortText: "1"
|
||||
sortText: SortText.JavascriptIdentifiers
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -169,28 +224,23 @@ namespace ts.Completions {
|
||||
const completionNameForLiteral = (literal: string | number | PseudoBigInt) =>
|
||||
typeof literal === "object" ? pseudoBigIntToString(literal) + "n" : JSON.stringify(literal);
|
||||
function createCompletionEntryForLiteral(literal: string | number | PseudoBigInt): CompletionEntry {
|
||||
return { name: completionNameForLiteral(literal), kind: ScriptElementKind.string, kindModifiers: ScriptElementKindModifier.none, sortText: "0" };
|
||||
return { name: completionNameForLiteral(literal), kind: ScriptElementKind.string, kindModifiers: ScriptElementKindModifier.none, sortText: SortText.LocationPriority };
|
||||
}
|
||||
|
||||
function createCompletionEntry(
|
||||
symbol: Symbol,
|
||||
sortText: SortText,
|
||||
location: Node | undefined,
|
||||
sourceFile: SourceFile,
|
||||
typeChecker: TypeChecker,
|
||||
target: ScriptTarget,
|
||||
kind: CompletionKind,
|
||||
name: string,
|
||||
needsConvertPropertyAccess: boolean,
|
||||
origin: SymbolOriginInfo | undefined,
|
||||
recommendedCompletion: Symbol | undefined,
|
||||
propertyAccessToConvert: PropertyAccessExpression | undefined,
|
||||
isJsxInitializer: IsJsxInitializer | undefined,
|
||||
preferences: UserPreferences,
|
||||
): CompletionEntry | undefined {
|
||||
const info = getCompletionEntryDisplayNameForSymbol(symbol, target, origin, kind);
|
||||
if (!info) {
|
||||
return undefined;
|
||||
}
|
||||
const { name, needsConvertPropertyAccess } = info;
|
||||
|
||||
let insertText: string | undefined;
|
||||
let replacementSpan: TextSpan | undefined;
|
||||
if (origin && origin.kind === SymbolOriginInfoKind.ThisType) {
|
||||
@@ -230,7 +280,7 @@ namespace ts.Completions {
|
||||
name,
|
||||
kind: SymbolDisplay.getSymbolKind(typeChecker, symbol, location!), // TODO: GH#18217
|
||||
kindModifiers: SymbolDisplay.getSymbolModifiers(symbol),
|
||||
sortText: "0",
|
||||
sortText,
|
||||
source: getSourceFromOrigin(origin),
|
||||
hasAction: trueOrUndefined(!!origin && originIsExport(origin)),
|
||||
isRecommended: trueOrUndefined(isRecommendedCompletionMatch(symbol, recommendedCompletion, typeChecker)),
|
||||
@@ -266,6 +316,7 @@ namespace ts.Completions {
|
||||
isJsxInitializer?: IsJsxInitializer,
|
||||
recommendedCompletion?: Symbol,
|
||||
symbolToOriginInfoMap?: SymbolOriginInfoMap,
|
||||
symbolToSortTextMap?: SymbolSortTextMap,
|
||||
): Map<true> {
|
||||
const start = timestamp();
|
||||
// Tracks unique names.
|
||||
@@ -275,13 +326,30 @@ namespace ts.Completions {
|
||||
const uniques = createMap<true>();
|
||||
for (const symbol of symbols) {
|
||||
const origin = symbolToOriginInfoMap ? symbolToOriginInfoMap[getSymbolId(symbol)] : undefined;
|
||||
const entry = createCompletionEntry(symbol, location, sourceFile, typeChecker, target, kind, origin, recommendedCompletion, propertyAccessToConvert, isJsxInitializer, preferences);
|
||||
if (!entry) {
|
||||
const info = getCompletionEntryDisplayNameForSymbol(symbol, target, origin, kind);
|
||||
if (!info) {
|
||||
continue;
|
||||
}
|
||||
const { name, needsConvertPropertyAccess } = info;
|
||||
if (uniques.has(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const { name } = entry;
|
||||
if (uniques.has(name)) {
|
||||
const entry = createCompletionEntry(
|
||||
symbol,
|
||||
symbolToSortTextMap && symbolToSortTextMap[getSymbolId(symbol)] || SortText.LocationPriority,
|
||||
location,
|
||||
sourceFile,
|
||||
typeChecker,
|
||||
name,
|
||||
needsConvertPropertyAccess,
|
||||
origin,
|
||||
recommendedCompletion,
|
||||
propertyAccessToConvert,
|
||||
isJsxInitializer,
|
||||
preferences
|
||||
);
|
||||
if (!entry) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -321,7 +389,7 @@ namespace ts.Completions {
|
||||
name,
|
||||
kindModifiers: ScriptElementKindModifier.none,
|
||||
kind: ScriptElementKind.label,
|
||||
sortText: "0"
|
||||
sortText: SortText.LocationPriority
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -358,7 +426,7 @@ namespace ts.Completions {
|
||||
// We don't need to perform character checks here because we're only comparing the
|
||||
// name against 'entryName' (which is known to be good), not building a new
|
||||
// completion entry.
|
||||
return firstDefined<Symbol, SymbolCompletion>(symbols, (symbol): SymbolCompletion | undefined => { // TODO: Shouldn't need return type annotation (GH#12632)
|
||||
return firstDefined(symbols, (symbol): SymbolCompletion | undefined => {
|
||||
const origin = symbolToOriginInfoMap[getSymbolId(symbol)];
|
||||
const info = getCompletionEntryDisplayNameForSymbol(symbol, compilerOptions.target!, origin, completionKind);
|
||||
return info && info.name === entryId.name && getSourceFromOrigin(origin) === entryId.source
|
||||
@@ -512,6 +580,7 @@ namespace ts.Completions {
|
||||
readonly previousToken: Node | undefined;
|
||||
readonly isJsxInitializer: IsJsxInitializer;
|
||||
readonly insideJsDocTagTypeExpression: boolean;
|
||||
readonly symbolToSortTextMap: SymbolSortTextMap;
|
||||
}
|
||||
type Request = { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag } | { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag };
|
||||
|
||||
@@ -804,6 +873,7 @@ namespace ts.Completions {
|
||||
let keywordFilters = KeywordCompletionFilters.None;
|
||||
let symbols: Symbol[] = [];
|
||||
const symbolToOriginInfoMap: SymbolOriginInfoMap = [];
|
||||
const symbolToSortTextMap: SymbolSortTextMap = [];
|
||||
|
||||
if (isRightOfDot) {
|
||||
getTypeScriptMemberSymbols();
|
||||
@@ -853,7 +923,8 @@ namespace ts.Completions {
|
||||
recommendedCompletion,
|
||||
previousToken,
|
||||
isJsxInitializer,
|
||||
insideJsDocTagTypeExpression
|
||||
insideJsDocTagTypeExpression,
|
||||
symbolToSortTextMap
|
||||
};
|
||||
|
||||
type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag;
|
||||
@@ -965,6 +1036,9 @@ namespace ts.Completions {
|
||||
symbolToOriginInfoMap[getSymbolId(firstAccessibleSymbol)] =
|
||||
!moduleSymbol || !isExternalModuleSymbol(moduleSymbol) ? { kind: SymbolOriginInfoKind.SymbolMemberNoExport } : { kind: SymbolOriginInfoKind.SymbolMemberExport, moduleSymbol, isDefaultExport: false };
|
||||
}
|
||||
else if (preferences.includeCompletionsWithInsertText) {
|
||||
symbols.push(symbol);
|
||||
}
|
||||
}
|
||||
else {
|
||||
symbols.push(symbol);
|
||||
@@ -1054,6 +1128,12 @@ namespace ts.Completions {
|
||||
const symbolMeanings = (isTypeOnly ? SymbolFlags.None : SymbolFlags.Value) | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias;
|
||||
|
||||
symbols = Debug.assertEachDefined(typeChecker.getSymbolsInScope(scopeNode, symbolMeanings), "getSymbolsInScope() should all be defined");
|
||||
for (const symbol of symbols) {
|
||||
if (!typeChecker.isArgumentsSymbol(symbol) &&
|
||||
!some(symbol.declarations, d => d.getSourceFile() === sourceFile)) {
|
||||
symbolToSortTextMap[getSymbolId(symbol)] = SortText.GlobalsOrKeywords;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to insert 'this.' before properties of `this` type, so only do that if `includeInsertTextCompletions`
|
||||
if (preferences.includeCompletionsWithInsertText && scopeNode.kind !== SyntaxKind.SourceFile) {
|
||||
@@ -1062,6 +1142,7 @@ namespace ts.Completions {
|
||||
for (const symbol of getPropertiesForCompletion(thisType, typeChecker)) {
|
||||
symbolToOriginInfoMap[getSymbolId(symbol)] = { kind: SymbolOriginInfoKind.ThisType };
|
||||
symbols.push(symbol);
|
||||
symbolToSortTextMap[getSymbolId(symbol)] = SortText.SuggestedClassMembers;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1195,6 +1276,7 @@ namespace ts.Completions {
|
||||
// So in `declare namespace foo {} declare module "foo" { export = foo; }`, there will just be the global completion for `foo`.
|
||||
some(resolvedModuleSymbol.declarations, d => !!d.getSourceFile().externalModuleIndicator)) {
|
||||
symbols.push(resolvedModuleSymbol);
|
||||
symbolToSortTextMap[getSymbolId(resolvedModuleSymbol)] = SortText.AutoImportSuggestions;
|
||||
symbolToOriginInfoMap[getSymbolId(resolvedModuleSymbol)] = { kind: SymbolOriginInfoKind.Export, moduleSymbol, isDefaultExport: false };
|
||||
}
|
||||
|
||||
@@ -1220,6 +1302,7 @@ namespace ts.Completions {
|
||||
const origin: SymbolOriginInfoExport = { kind: SymbolOriginInfoKind.Export, moduleSymbol, isDefaultExport };
|
||||
if (detailsEntryId || stringContainsCharactersInOrder(getSymbolName(symbol, origin, target).toLowerCase(), tokenTextLowerCase)) {
|
||||
symbols.push(symbol);
|
||||
symbolToSortTextMap[getSymbolId(symbol)] = SortText.AutoImportSuggestions;
|
||||
symbolToOriginInfoMap[getSymbolId(symbol)] = origin;
|
||||
}
|
||||
}
|
||||
@@ -1941,7 +2024,7 @@ namespace ts.Completions {
|
||||
name: tokenToString(i)!,
|
||||
kind: ScriptElementKind.keyword,
|
||||
kindModifiers: ScriptElementKindModifier.none,
|
||||
sortText: "0"
|
||||
sortText: SortText.GlobalsOrKeywords
|
||||
});
|
||||
}
|
||||
return res;
|
||||
|
||||
@@ -16,9 +16,15 @@ namespace ts.FindAllReferences {
|
||||
export const enum EntryKind { Span, Node, StringLiteral, SearchedLocalFoundProperty, SearchedPropertyFoundLocal }
|
||||
export type NodeEntryKind = EntryKind.Node | EntryKind.StringLiteral | EntryKind.SearchedLocalFoundProperty | EntryKind.SearchedPropertyFoundLocal;
|
||||
export type Entry = NodeEntry | SpanEntry;
|
||||
export interface ContextWithStartAndEndNode {
|
||||
start: Node;
|
||||
end: Node;
|
||||
}
|
||||
export type ContextNode = Node | ContextWithStartAndEndNode;
|
||||
export interface NodeEntry {
|
||||
readonly kind: NodeEntryKind;
|
||||
readonly node: Node;
|
||||
readonly context?: ContextNode;
|
||||
}
|
||||
export interface SpanEntry {
|
||||
readonly kind: EntryKind.Span;
|
||||
@@ -26,7 +32,143 @@ namespace ts.FindAllReferences {
|
||||
readonly textSpan: TextSpan;
|
||||
}
|
||||
export function nodeEntry(node: Node, kind: NodeEntryKind = EntryKind.Node): NodeEntry {
|
||||
return { kind, node: (node as NamedDeclaration).name || node };
|
||||
return {
|
||||
kind,
|
||||
node: (node as NamedDeclaration).name || node,
|
||||
context: getContextNodeForNodeEntry(node)
|
||||
};
|
||||
}
|
||||
|
||||
export function isContextWithStartAndEndNode(node: ContextNode): node is ContextWithStartAndEndNode {
|
||||
return node && (node as Node).kind === undefined;
|
||||
}
|
||||
|
||||
function getContextNodeForNodeEntry(node: Node): ContextNode | undefined {
|
||||
if (isDeclaration(node)) {
|
||||
return getContextNode(node);
|
||||
}
|
||||
|
||||
if (!node.parent) return undefined;
|
||||
|
||||
if (!isDeclaration(node.parent) && !isExportAssignment(node.parent)) {
|
||||
// Special property assignment in javascript
|
||||
if (isInJSFile(node)) {
|
||||
const binaryExpression = isBinaryExpression(node.parent) ?
|
||||
node.parent :
|
||||
isPropertyAccessExpression(node.parent) &&
|
||||
isBinaryExpression(node.parent.parent) &&
|
||||
node.parent.parent.left === node.parent ?
|
||||
node.parent.parent :
|
||||
undefined;
|
||||
if (binaryExpression && getAssignmentDeclarationKind(binaryExpression) !== AssignmentDeclarationKind.None) {
|
||||
return getContextNode(binaryExpression);
|
||||
}
|
||||
}
|
||||
|
||||
// Jsx Tags
|
||||
if (isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) {
|
||||
return node.parent.parent;
|
||||
}
|
||||
else if (isJsxSelfClosingElement(node.parent) ||
|
||||
isLabeledStatement(node.parent) ||
|
||||
isBreakOrContinueStatement(node.parent)) {
|
||||
return node.parent;
|
||||
}
|
||||
else if (isStringLiteralLike(node)) {
|
||||
const validImport = tryGetImportFromModuleSpecifier(node);
|
||||
if (validImport) {
|
||||
const declOrStatement = findAncestor(validImport, node =>
|
||||
isDeclaration(node) ||
|
||||
isStatement(node) ||
|
||||
isJSDocTag(node)
|
||||
)! as NamedDeclaration | Statement | JSDocTag;
|
||||
return isDeclaration(declOrStatement) ?
|
||||
getContextNode(declOrStatement) :
|
||||
declOrStatement;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle computed property name
|
||||
const propertyName = findAncestor(node, isComputedPropertyName);
|
||||
return propertyName ?
|
||||
getContextNode(propertyName.parent) :
|
||||
undefined;
|
||||
}
|
||||
|
||||
if (node.parent.name === node || // node is name of declaration, use parent
|
||||
isConstructorDeclaration(node.parent) ||
|
||||
isExportAssignment(node.parent) ||
|
||||
// Property name of the import export specifier or binding pattern, use parent
|
||||
((isImportOrExportSpecifier(node.parent) || isBindingElement(node.parent))
|
||||
&& node.parent.propertyName === node) ||
|
||||
// Is default export
|
||||
(node.kind === SyntaxKind.DefaultKeyword && hasModifier(node.parent, ModifierFlags.ExportDefault))) {
|
||||
return getContextNode(node.parent);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getContextNode(node: NamedDeclaration | BinaryExpression | ForInOrOfStatement | undefined): ContextNode | undefined {
|
||||
if (!node) return undefined;
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
return !isVariableDeclarationList(node.parent) || node.parent.declarations.length !== 1 ?
|
||||
node :
|
||||
isVariableStatement(node.parent.parent) ?
|
||||
node.parent.parent :
|
||||
isForInOrOfStatement(node.parent.parent) ?
|
||||
getContextNode(node.parent.parent) :
|
||||
node.parent;
|
||||
|
||||
case SyntaxKind.BindingElement:
|
||||
return getContextNode(node.parent.parent as NamedDeclaration);
|
||||
|
||||
case SyntaxKind.ImportSpecifier:
|
||||
return node.parent.parent.parent;
|
||||
|
||||
case SyntaxKind.ExportSpecifier:
|
||||
case SyntaxKind.NamespaceImport:
|
||||
return node.parent.parent;
|
||||
|
||||
case SyntaxKind.ImportClause:
|
||||
return node.parent;
|
||||
|
||||
case SyntaxKind.BinaryExpression:
|
||||
return isExpressionStatement(node.parent) ?
|
||||
node.parent :
|
||||
node;
|
||||
|
||||
case SyntaxKind.ForOfStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
return {
|
||||
start: (node as ForInOrOfStatement).initializer,
|
||||
end: (node as ForInOrOfStatement).expression
|
||||
};
|
||||
|
||||
case SyntaxKind.PropertyAssignment:
|
||||
case SyntaxKind.ShorthandPropertyAssignment:
|
||||
return isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent) ?
|
||||
getContextNode(
|
||||
findAncestor(node.parent, node =>
|
||||
isBinaryExpression(node) || isForInOrOfStatement(node)
|
||||
) as BinaryExpression | ForInOrOfStatement
|
||||
) :
|
||||
node;
|
||||
|
||||
default:
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
export function toContextSpan(textSpan: TextSpan, sourceFile: SourceFile, context?: ContextNode): { contextSpan: TextSpan } | undefined {
|
||||
if (!context) return undefined;
|
||||
const contextSpan = isContextWithStartAndEndNode(context) ?
|
||||
getTextSpan(context.start, sourceFile, context.end) :
|
||||
getTextSpan(context, sourceFile);
|
||||
return contextSpan.start !== textSpan.start || contextSpan.length !== textSpan.length ?
|
||||
{ contextSpan } :
|
||||
undefined;
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
@@ -123,7 +265,16 @@ namespace ts.FindAllReferences {
|
||||
const { symbol } = def;
|
||||
const { displayParts, kind } = getDefinitionKindAndDisplayParts(symbol, checker, originalNode);
|
||||
const name = displayParts.map(p => p.text).join("");
|
||||
return { node: symbol.declarations ? getNameOfDeclaration(first(symbol.declarations)) || first(symbol.declarations) : originalNode, name, kind, displayParts };
|
||||
const declaration = symbol.declarations ? first(symbol.declarations) : undefined;
|
||||
return {
|
||||
node: declaration ?
|
||||
getNameOfDeclaration(declaration) || declaration :
|
||||
originalNode,
|
||||
name,
|
||||
kind,
|
||||
displayParts,
|
||||
context: getContextNode(declaration)
|
||||
};
|
||||
}
|
||||
case DefinitionKind.Label: {
|
||||
const { node } = def;
|
||||
@@ -150,9 +301,19 @@ namespace ts.FindAllReferences {
|
||||
}
|
||||
})();
|
||||
|
||||
const { node, name, kind, displayParts } = info;
|
||||
const { node, name, kind, displayParts, context } = info;
|
||||
const sourceFile = node.getSourceFile();
|
||||
return { containerKind: ScriptElementKind.unknown, containerName: "", fileName: sourceFile.fileName, kind, name, textSpan: getTextSpan(isComputedPropertyName(node) ? node.expression : node, sourceFile), displayParts };
|
||||
const textSpan = getTextSpan(isComputedPropertyName(node) ? node.expression : node, sourceFile);
|
||||
return {
|
||||
containerKind: ScriptElementKind.unknown,
|
||||
containerName: "",
|
||||
fileName: sourceFile.fileName,
|
||||
kind,
|
||||
name,
|
||||
textSpan,
|
||||
displayParts,
|
||||
...toContextSpan(textSpan, sourceFile, context)
|
||||
};
|
||||
}
|
||||
|
||||
function getDefinitionKindAndDisplayParts(symbol: Symbol, checker: TypeChecker, node: Node): { displayParts: SymbolDisplayPart[], kind: ScriptElementKind } {
|
||||
@@ -168,14 +329,13 @@ namespace ts.FindAllReferences {
|
||||
}
|
||||
|
||||
export function toReferenceEntry(entry: Entry): ReferenceEntry {
|
||||
const { textSpan, fileName } = entryToDocumentSpan(entry);
|
||||
const documentSpan = entryToDocumentSpan(entry);
|
||||
if (entry.kind === EntryKind.Span) {
|
||||
return { textSpan, fileName, isWriteAccess: false, isDefinition: false };
|
||||
return { ...documentSpan, isWriteAccess: false, isDefinition: false };
|
||||
}
|
||||
const { kind, node } = entry;
|
||||
return {
|
||||
textSpan,
|
||||
fileName,
|
||||
...documentSpan,
|
||||
isWriteAccess: isWriteAccessForReference(node),
|
||||
isDefinition: node.kind === SyntaxKind.DefaultKeyword
|
||||
|| !!getDeclarationFromName(node)
|
||||
@@ -190,7 +350,12 @@ namespace ts.FindAllReferences {
|
||||
}
|
||||
else {
|
||||
const sourceFile = entry.node.getSourceFile();
|
||||
return { textSpan: getTextSpan(entry.node, sourceFile), fileName: sourceFile.fileName };
|
||||
const textSpan = getTextSpan(entry.node, sourceFile);
|
||||
return {
|
||||
textSpan,
|
||||
fileName: sourceFile.fileName,
|
||||
...toContextSpan(textSpan, sourceFile, entry.context)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,14 +388,16 @@ namespace ts.FindAllReferences {
|
||||
}
|
||||
|
||||
function toImplementationLocation(entry: Entry, checker: TypeChecker): ImplementationLocation {
|
||||
const documentSpan = entryToDocumentSpan(entry);
|
||||
if (entry.kind !== EntryKind.Span) {
|
||||
const { node } = entry;
|
||||
const sourceFile = node.getSourceFile();
|
||||
return { textSpan: getTextSpan(node, sourceFile), fileName: sourceFile.fileName, ...implementationKindDisplayParts(node, checker) };
|
||||
return {
|
||||
...documentSpan,
|
||||
...implementationKindDisplayParts(node, checker)
|
||||
};
|
||||
}
|
||||
else {
|
||||
const { textSpan, fileName } = entry;
|
||||
return { textSpan, fileName, kind: ScriptElementKind.unknown, displayParts: [] };
|
||||
return { ...documentSpan, kind: ScriptElementKind.unknown, displayParts: [] };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,26 +424,32 @@ namespace ts.FindAllReferences {
|
||||
}
|
||||
|
||||
export function toHighlightSpan(entry: Entry): { fileName: string, span: HighlightSpan } {
|
||||
const documentSpan = entryToDocumentSpan(entry);
|
||||
if (entry.kind === EntryKind.Span) {
|
||||
const { fileName, textSpan } = entry;
|
||||
return { fileName, span: { textSpan, kind: HighlightSpanKind.reference } };
|
||||
return {
|
||||
fileName: documentSpan.fileName,
|
||||
span: {
|
||||
textSpan: documentSpan.textSpan,
|
||||
kind: HighlightSpanKind.reference
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const { node, kind } = entry;
|
||||
const sourceFile = node.getSourceFile();
|
||||
const writeAccess = isWriteAccessForReference(node);
|
||||
const writeAccess = isWriteAccessForReference(entry.node);
|
||||
const span: HighlightSpan = {
|
||||
textSpan: getTextSpan(node, sourceFile),
|
||||
textSpan: documentSpan.textSpan,
|
||||
kind: writeAccess ? HighlightSpanKind.writtenReference : HighlightSpanKind.reference,
|
||||
isInString: kind === EntryKind.StringLiteral ? true : undefined,
|
||||
isInString: entry.kind === EntryKind.StringLiteral ? true : undefined,
|
||||
...documentSpan.contextSpan && { contextSpan: documentSpan.contextSpan }
|
||||
};
|
||||
return { fileName: sourceFile.fileName, span };
|
||||
return { fileName: documentSpan.fileName, span };
|
||||
}
|
||||
|
||||
function getTextSpan(node: Node, sourceFile: SourceFile): TextSpan {
|
||||
function getTextSpan(node: Node, sourceFile: SourceFile, endNode?: Node): TextSpan {
|
||||
let start = node.getStart(sourceFile);
|
||||
let end = node.getEnd();
|
||||
let end = (endNode || node).getEnd();
|
||||
if (node.kind === SyntaxKind.StringLiteral) {
|
||||
Debug.assert(endNode === undefined);
|
||||
start += 1;
|
||||
end -= 1;
|
||||
}
|
||||
@@ -635,7 +808,7 @@ namespace ts.FindAllReferences.Core {
|
||||
// Ignore UMD module and global merge
|
||||
if (symbol.flags & SymbolFlags.Transient) return undefined;
|
||||
// Assertions for GH#21814. We should be handling SourceFile symbols in `getReferencedSymbolsForModule` instead of getting here.
|
||||
Debug.fail(`Unexpected symbol at ${Debug.showSyntaxKind(node)}: ${Debug.showSymbol(symbol)}`);
|
||||
Debug.fail(`Unexpected symbol at ${Debug.formatSyntaxKind(node.kind)}: ${Debug.formatSymbol(symbol)}`);
|
||||
}
|
||||
return isTypeLiteralNode(decl.parent) && isUnionTypeNode(decl.parent.parent)
|
||||
? checker.getPropertyOfType(checker.getTypeFromTypeNode(decl.parent.parent), symbol.name)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# How does TypeScript formatting work?
|
||||
|
||||
To format code you need to have a formatting context and a `SourceFile`. The formatting context contains
|
||||
all user settings like tab size, newline character, etc.
|
||||
|
||||
The end result of formatting is represented by TextChange objects which hold the new string content, and
|
||||
the text to replace it with.
|
||||
|
||||
```ts
|
||||
export interface TextChange {
|
||||
span: TextSpan; // start, length
|
||||
newText: string;
|
||||
}
|
||||
```
|
||||
|
||||
## Internals
|
||||
|
||||
Most of the exposed APIs internally are `format*` and they all set up and configure `formatSpan` which could be considered the root call for formatting. Span in this case refers to the range of
|
||||
the sourcefile which should be formatted.
|
||||
|
||||
The formatSpan then uses a scanner (either with or without JSX support) which starts at the highest
|
||||
node the covers the span of text and recurses down through the node's children.
|
||||
|
||||
As it recurses, `processNode` is called on the children setting the indentation is decided and passed
|
||||
through into each of that node's children.
|
||||
|
||||
The meat of formatting decisions is made via `processPair`, the pair here being the current node and the previous node. `processPair` which mutates the formatting context to represent the current place in the scanner and requests a set of rules which can be applied to the items via `createRulesMap`.
|
||||
|
||||
There are a lot of rules, which you can find in [rules.ts](./rules.ts) each one has a left and right reference to nodes or token ranges and note of what action should be applied by the formatter.
|
||||
|
||||
### Where is this used?
|
||||
|
||||
The formatter is used mainly from any language service operation that inserts or modifies code. The formatter is not exported publicly, and so all usage can only come through the language server.
|
||||
@@ -229,6 +229,7 @@ namespace ts.formatting {
|
||||
|
||||
rule("NoSpaceBeforeNonNullAssertionOperator", anyToken, SyntaxKind.ExclamationToken, [isNonJsxSameLineTokenContext, isNonNullAssertionContext], RuleAction.Delete),
|
||||
rule("NoSpaceAfterNewKeywordOnConstructorSignature", SyntaxKind.NewKeyword, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isConstructorSignatureContext], RuleAction.Delete),
|
||||
rule("SpaceLessThanAndNonJSXTypeAnnotation", SyntaxKind.LessThanToken, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext], RuleAction.Space),
|
||||
];
|
||||
|
||||
// These rules are applied after high priority
|
||||
@@ -349,6 +350,18 @@ namespace ts.formatting {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* A rule takes a two tokens (left/right) and a particular context
|
||||
* for which you're meant to look at them. You then declare what should the
|
||||
* whitespace annotation be between these tokens via the action param.
|
||||
*
|
||||
* @param debugName Name to print
|
||||
* @param left The left side of the comparison
|
||||
* @param right The right side of the comparison
|
||||
* @param context A set of filters to narrow down the space in which this formatter rule applies
|
||||
* @param action a declaration of the expected whitespace
|
||||
* @param flags whether the rule deletes a line or not, defaults to no-op
|
||||
*/
|
||||
function rule(
|
||||
debugName: string,
|
||||
left: SyntaxKind | ReadonlyArray<SyntaxKind> | TokenRange,
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace ts.GoToDefinition {
|
||||
export function getReferenceAtPosition(sourceFile: SourceFile, position: number, program: Program): { fileName: string, file: SourceFile } | undefined {
|
||||
const referencePath = findReferenceInPosition(sourceFile.referencedFiles, position);
|
||||
if (referencePath) {
|
||||
const file = tryResolveScriptReference(program, sourceFile, referencePath);
|
||||
const file = program.getSourceFileFromReference(sourceFile, referencePath);
|
||||
return file && { fileName: referencePath.fileName, file };
|
||||
}
|
||||
|
||||
@@ -273,13 +273,19 @@ namespace ts.GoToDefinition {
|
||||
function createDefinitionInfoFromName(declaration: Declaration, symbolKind: ScriptElementKind, symbolName: string, containerName: string): DefinitionInfo {
|
||||
const name = getNameOfDeclaration(declaration) || declaration;
|
||||
const sourceFile = name.getSourceFile();
|
||||
const textSpan = createTextSpanFromNode(name, sourceFile);
|
||||
return {
|
||||
fileName: sourceFile.fileName,
|
||||
textSpan: createTextSpanFromNode(name, sourceFile),
|
||||
textSpan,
|
||||
kind: symbolKind,
|
||||
name: symbolName,
|
||||
containerKind: undefined!, // TODO: GH#18217
|
||||
containerName
|
||||
containerName,
|
||||
...FindAllReferences.toContextSpan(
|
||||
textSpan,
|
||||
sourceFile,
|
||||
FindAllReferences.getContextNode(declaration)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ namespace ts.FindAllReferences {
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.assertNever(direct, `Unexpected import kind: ${Debug.showSyntaxKind(direct)}`);
|
||||
Debug.failBadSyntaxKind(direct, "Unexpected import kind.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -515,7 +515,7 @@ namespace ts.FindAllReferences {
|
||||
const sym = useLhsSymbol ? checker.getSymbolAtLocation(cast(node.left, isPropertyAccessExpression).name) : symbol;
|
||||
// Better detection for GH#20803
|
||||
if (sym && !(checker.getMergedSymbol(sym.parent!).flags & SymbolFlags.Module)) {
|
||||
Debug.fail(`Special property assignment kind does not have a module as its parent. Assignment is ${Debug.showSymbol(sym)}, parent is ${Debug.showSymbol(sym.parent!)}`);
|
||||
Debug.fail(`Special property assignment kind does not have a module as its parent. Assignment is ${Debug.formatSymbol(sym)}, parent is ${Debug.formatSymbol(sym.parent!)}`);
|
||||
}
|
||||
return sym && exportInfo(sym, kind);
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace ts.OrganizeImports {
|
||||
const usedImports: ImportDeclaration[] = [];
|
||||
|
||||
for (const importDecl of oldImports) {
|
||||
const {importClause} = importDecl;
|
||||
const { importClause, moduleSpecifier } = importDecl;
|
||||
|
||||
if (!importClause) {
|
||||
// Imports without import clauses are assumed to be included for their side effects and are not removed.
|
||||
@@ -125,6 +125,23 @@ namespace ts.OrganizeImports {
|
||||
if (name || namedBindings) {
|
||||
usedImports.push(updateImportDeclarationAndClause(importDecl, name, namedBindings));
|
||||
}
|
||||
// If a module is imported to be augmented, it’s used
|
||||
else if (hasModuleDeclarationMatchingSpecifier(sourceFile, moduleSpecifier)) {
|
||||
// If we’re in a declaration file, it’s safe to remove the import clause from it
|
||||
if (sourceFile.isDeclarationFile) {
|
||||
usedImports.push(createImportDeclaration(
|
||||
importDecl.decorators,
|
||||
importDecl.modifiers,
|
||||
/*importClause*/ undefined,
|
||||
moduleSpecifier));
|
||||
}
|
||||
// If we’re not in a declaration file, we can’t remove the import clause even though
|
||||
// the imported symbols are unused, because removing them makes it look like the import
|
||||
// declaration has side effects, which will cause it to be preserved in the JS emit.
|
||||
else {
|
||||
usedImports.push(importDecl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return usedImports;
|
||||
@@ -135,6 +152,13 @@ namespace ts.OrganizeImports {
|
||||
}
|
||||
}
|
||||
|
||||
function hasModuleDeclarationMatchingSpecifier(sourceFile: SourceFile, moduleSpecifier: Expression) {
|
||||
const moduleSpecifierText = isStringLiteral(moduleSpecifier) && moduleSpecifier.text;
|
||||
return isString(moduleSpecifierText) && some(sourceFile.moduleAugmentations, moduleName =>
|
||||
isStringLiteral(moduleName)
|
||||
&& moduleName.text === moduleSpecifierText);
|
||||
}
|
||||
|
||||
function getExternalModuleName(specifier: Expression) {
|
||||
return specifier !== undefined && isStringLiteralLike(specifier)
|
||||
? specifier.text
|
||||
|
||||
@@ -237,7 +237,7 @@ namespace ts.OutliningElementsCollector {
|
||||
? findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile)
|
||||
: findChildOfKind(body, SyntaxKind.OpenBraceToken, sourceFile);
|
||||
const closeToken = findChildOfKind(body, SyntaxKind.CloseBraceToken, sourceFile);
|
||||
return openToken && closeToken && spanBetweenTokens(openToken, closeToken, node.parent, sourceFile, /*autoCollapse*/ node.parent.kind !== SyntaxKind.ArrowFunction);
|
||||
return openToken && closeToken && spanBetweenTokens(openToken, closeToken, node, sourceFile, /*autoCollapse*/ node.kind !== SyntaxKind.ArrowFunction);
|
||||
}
|
||||
|
||||
function spanBetweenTokens(openToken: Node, closeToken: Node, hintSpanNode: Node, sourceFile: SourceFile, autoCollapse = false, useFullStart = true): OutliningSpan {
|
||||
|
||||
+39
-16
@@ -173,7 +173,7 @@ namespace ts {
|
||||
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`);
|
||||
Debug.fail(`Did not expect ${Debug.formatSyntaxKind(parent.kind)} to have an Identifier in its trivia`);
|
||||
}
|
||||
nodes.push(createNode(token, pos, textPos, parent));
|
||||
}
|
||||
@@ -1467,33 +1467,41 @@ namespace ts {
|
||||
}
|
||||
|
||||
const typeChecker = program.getTypeChecker();
|
||||
const symbol = getSymbolAtLocationForQuickInfo(node, typeChecker);
|
||||
const nodeForQuickInfo = getNodeForQuickInfo(node);
|
||||
const symbol = getSymbolAtLocationForQuickInfo(nodeForQuickInfo, typeChecker);
|
||||
|
||||
if (!symbol || typeChecker.isUnknownSymbol(symbol)) {
|
||||
const type = shouldGetType(sourceFile, node, position) ? typeChecker.getTypeAtLocation(node) : undefined;
|
||||
const type = shouldGetType(sourceFile, nodeForQuickInfo, position) ? typeChecker.getTypeAtLocation(nodeForQuickInfo) : undefined;
|
||||
return type && {
|
||||
kind: ScriptElementKind.unknown,
|
||||
kindModifiers: ScriptElementKindModifier.none,
|
||||
textSpan: createTextSpanFromNode(node, sourceFile),
|
||||
displayParts: typeChecker.runWithCancellationToken(cancellationToken, typeChecker => typeToDisplayParts(typeChecker, type, getContainerNode(node))),
|
||||
textSpan: createTextSpanFromNode(nodeForQuickInfo, sourceFile),
|
||||
displayParts: typeChecker.runWithCancellationToken(cancellationToken, typeChecker => typeToDisplayParts(typeChecker, type, getContainerNode(nodeForQuickInfo))),
|
||||
documentation: type.symbol ? type.symbol.getDocumentationComment(typeChecker) : undefined,
|
||||
tags: type.symbol ? type.symbol.getJsDocTags() : undefined
|
||||
};
|
||||
}
|
||||
|
||||
const { symbolKind, displayParts, documentation, tags } = typeChecker.runWithCancellationToken(cancellationToken, typeChecker =>
|
||||
SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, getContainerNode(node), node)
|
||||
SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, getContainerNode(nodeForQuickInfo), nodeForQuickInfo)
|
||||
);
|
||||
return {
|
||||
kind: symbolKind,
|
||||
kindModifiers: SymbolDisplay.getSymbolModifiers(symbol),
|
||||
textSpan: createTextSpanFromNode(node, sourceFile),
|
||||
textSpan: createTextSpanFromNode(nodeForQuickInfo, sourceFile),
|
||||
displayParts,
|
||||
documentation,
|
||||
tags,
|
||||
};
|
||||
}
|
||||
|
||||
function getNodeForQuickInfo(node: Node): Node {
|
||||
if (isNewExpression(node.parent) && node.pos === node.parent.pos) {
|
||||
return node.parent.expression;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
function shouldGetType(sourceFile: SourceFile, node: Node, position: number): boolean {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.Identifier:
|
||||
@@ -1536,13 +1544,17 @@ namespace ts {
|
||||
|
||||
/// References and Occurrences
|
||||
function getOccurrencesAtPosition(fileName: string, position: number): ReadonlyArray<ReferenceEntry> | undefined {
|
||||
return flatMap(getDocumentHighlights(fileName, position, [fileName]), entry => entry.highlightSpans.map<ReferenceEntry>(highlightSpan => ({
|
||||
fileName: entry.fileName,
|
||||
textSpan: highlightSpan.textSpan,
|
||||
isWriteAccess: highlightSpan.kind === HighlightSpanKind.writtenReference,
|
||||
isDefinition: false,
|
||||
isInString: highlightSpan.isInString,
|
||||
})));
|
||||
return flatMap(
|
||||
getDocumentHighlights(fileName, position, [fileName]),
|
||||
entry => entry.highlightSpans.map<ReferenceEntry>(highlightSpan => ({
|
||||
fileName: entry.fileName,
|
||||
textSpan: highlightSpan.textSpan,
|
||||
isWriteAccess: highlightSpan.kind === HighlightSpanKind.writtenReference,
|
||||
isDefinition: false,
|
||||
...highlightSpan.isInString && { isInString: true },
|
||||
...highlightSpan.contextSpan && { contextSpan: highlightSpan.contextSpan }
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
function getDocumentHighlights(fileName: string, position: number, filesToSearch: ReadonlyArray<string>): DocumentHighlights[] | undefined {
|
||||
@@ -1560,8 +1572,14 @@ namespace ts {
|
||||
const node = getTouchingPropertyName(sourceFile, position);
|
||||
if (isIdentifier(node) && (isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) && isIntrinsicJsxName(node.escapedText)) {
|
||||
const { openingElement, closingElement } = node.parent.parent;
|
||||
return [openingElement, closingElement].map((node): RenameLocation =>
|
||||
({ fileName: sourceFile.fileName, textSpan: createTextSpanFromNode(node.tagName, sourceFile) }));
|
||||
return [openingElement, closingElement].map((node): RenameLocation => {
|
||||
const textSpan = createTextSpanFromNode(node.tagName, sourceFile);
|
||||
return {
|
||||
fileName: sourceFile.fileName,
|
||||
textSpan,
|
||||
...FindAllReferences.toContextSpan(textSpan, sourceFile, node.parent)
|
||||
};
|
||||
});
|
||||
}
|
||||
else {
|
||||
return getReferencesWorker(node, position, { findInStrings, findInComments, providePrefixAndSuffixTextForRename, isForRename: true },
|
||||
@@ -2072,6 +2090,10 @@ namespace ts {
|
||||
};
|
||||
}
|
||||
|
||||
function getSmartSelectionRange(fileName: string, position: number): SelectionRange {
|
||||
return SmartSelectionRange.getSmartSelectionRange(position, syntaxTreeCache.getCurrentSourceFile(fileName));
|
||||
}
|
||||
|
||||
function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences = emptyOptions): ApplicableRefactorInfo[] {
|
||||
synchronizeHostData();
|
||||
const file = getValidSourceFile(fileName);
|
||||
@@ -2119,6 +2141,7 @@ namespace ts {
|
||||
getBreakpointStatementAtPosition,
|
||||
getNavigateToItems,
|
||||
getRenameInfo,
|
||||
getSmartSelectionRange,
|
||||
findRenameLocations,
|
||||
getNavigationBarItems,
|
||||
getNavigationTree,
|
||||
|
||||
@@ -165,6 +165,7 @@ namespace ts {
|
||||
* { canRename: boolean, localizedErrorMessage: string, displayName: string, fullDisplayName: string, kind: string, kindModifiers: string, triggerSpan: { start; length } }
|
||||
*/
|
||||
getRenameInfo(fileName: string, position: number, options?: RenameInfoOptions): string;
|
||||
getSmartSelectionRange(fileName: string, position: number): string;
|
||||
|
||||
/**
|
||||
* Returns a JSON-encoded value of the type:
|
||||
@@ -838,6 +839,13 @@ namespace ts {
|
||||
);
|
||||
}
|
||||
|
||||
public getSmartSelectionRange(fileName: string, position: number): string {
|
||||
return this.forwardJSONCall(
|
||||
`getSmartSelectionRange('${fileName}', ${position})`,
|
||||
() => this.languageService.getSmartSelectionRange(fileName, position)
|
||||
);
|
||||
}
|
||||
|
||||
public findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): string {
|
||||
return this.forwardJSONCall(
|
||||
`findRenameLocations('${fileName}', ${position}, ${findInStrings}, ${findInComments}, ${providePrefixAndSuffixTextForRename})`,
|
||||
|
||||
@@ -133,11 +133,21 @@ namespace ts.SignatureHelp {
|
||||
}
|
||||
|
||||
function containsPrecedingToken(startingToken: Node, sourceFile: SourceFile, container: Node) {
|
||||
const precedingToken = Debug.assertDefined(
|
||||
findPrecedingToken(startingToken.getFullStart(), sourceFile, startingToken.parent, /*excludeJsdoc*/ true)
|
||||
);
|
||||
|
||||
return rangeContainsRange(container, precedingToken);
|
||||
const pos = startingToken.getFullStart();
|
||||
// There’s a possibility that `startingToken.parent` contains only `startingToken` and
|
||||
// missing nodes, none of which are valid to be returned by `findPrecedingToken`. In that
|
||||
// case, the preceding token we want is actually higher up the tree—almost definitely the
|
||||
// next parent, but theoretically the situation with missing nodes might be happening on
|
||||
// multiple nested levels.
|
||||
let currentParent: Node | undefined = startingToken.parent;
|
||||
while (currentParent) {
|
||||
const precedingToken = findPrecedingToken(pos, sourceFile, currentParent, /*excludeJsdoc*/ true);
|
||||
if (precedingToken) {
|
||||
return rangeContainsRange(container, precedingToken);
|
||||
}
|
||||
currentParent = currentParent.parent;
|
||||
}
|
||||
return Debug.fail("Could not find preceding token");
|
||||
}
|
||||
|
||||
export interface ArgumentInfoForCompletions {
|
||||
@@ -455,7 +465,7 @@ namespace ts.SignatureHelp {
|
||||
for (let n = node; !isSourceFile(n) && (isManuallyInvoked || !isBlock(n)); n = n.parent) {
|
||||
// If the node is not a subspan of its parent, this is a big problem.
|
||||
// There have been crashes that might be caused by this violation.
|
||||
Debug.assert(rangeContainsRange(n.parent, n), "Not a subspan", () => `Child: ${Debug.showSyntaxKind(n)}, parent: ${Debug.showSyntaxKind(n.parent)}`);
|
||||
Debug.assert(rangeContainsRange(n.parent, n), "Not a subspan", () => `Child: ${Debug.formatSyntaxKind(n.kind)}, parent: ${Debug.formatSyntaxKind(n.parent.kind)}`);
|
||||
const argumentInfo = getImmediatelyContainingArgumentOrContextualParameterInfo(n, position, sourceFile, checker);
|
||||
if (argumentInfo) {
|
||||
return argumentInfo;
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
/* @internal */
|
||||
namespace ts.SmartSelectionRange {
|
||||
export function getSmartSelectionRange(pos: number, sourceFile: SourceFile): SelectionRange {
|
||||
let selectionRange: SelectionRange = {
|
||||
textSpan: createTextSpanFromBounds(sourceFile.getFullStart(), sourceFile.getEnd())
|
||||
};
|
||||
|
||||
let parentNode: Node = sourceFile;
|
||||
outer: while (true) {
|
||||
const children = getSelectionChildren(parentNode);
|
||||
if (!children.length) break;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const prevNode: Node | undefined = children[i - 1];
|
||||
const node: Node = children[i];
|
||||
const nextNode: Node | undefined = children[i + 1];
|
||||
if (node.getStart(sourceFile) > pos) {
|
||||
break outer;
|
||||
}
|
||||
|
||||
if (positionShouldSnapToNode(pos, node, nextNode)) {
|
||||
// 1. Blocks are effectively redundant with SyntaxLists.
|
||||
// 2. TemplateSpans, along with the SyntaxLists containing them, are a somewhat unintuitive grouping
|
||||
// of things that should be considered independently.
|
||||
// 3. A VariableStatement’s children are just a VaraiableDeclarationList and a semicolon.
|
||||
// 4. A lone VariableDeclaration in a VaraibleDeclaration feels redundant with the VariableStatement.
|
||||
//
|
||||
// Dive in without pushing a selection range.
|
||||
if (isBlock(node)
|
||||
|| isTemplateSpan(node) || isTemplateHead(node)
|
||||
|| prevNode && isTemplateHead(prevNode)
|
||||
|| isVariableDeclarationList(node) && isVariableStatement(parentNode)
|
||||
|| isSyntaxList(node) && isVariableDeclarationList(parentNode)
|
||||
|| isVariableDeclaration(node) && isSyntaxList(parentNode) && children.length === 1) {
|
||||
parentNode = node;
|
||||
break;
|
||||
}
|
||||
|
||||
// Synthesize a stop for '${ ... }' since '${' and '}' actually belong to siblings.
|
||||
if (isTemplateSpan(parentNode) && nextNode && isTemplateMiddleOrTemplateTail(nextNode)) {
|
||||
const start = node.getFullStart() - "${".length;
|
||||
const end = nextNode.getStart() + "}".length;
|
||||
pushSelectionRange(start, end);
|
||||
}
|
||||
|
||||
// Blocks with braces, brackets, parens, or JSX tags on separate lines should be
|
||||
// selected from open to close, including whitespace but not including the braces/etc. themselves.
|
||||
const isBetweenMultiLineBookends = isSyntaxList(node)
|
||||
&& isListOpener(prevNode)
|
||||
&& isListCloser(nextNode)
|
||||
&& !positionsAreOnSameLine(prevNode.getStart(), nextNode.getStart(), sourceFile);
|
||||
const jsDocCommentStart = hasJSDocNodes(node) && node.jsDoc![0].getStart();
|
||||
const start = isBetweenMultiLineBookends ? prevNode.getEnd() : node.getStart();
|
||||
const end = isBetweenMultiLineBookends ? nextNode.getStart() : node.getEnd();
|
||||
if (isNumber(jsDocCommentStart)) {
|
||||
pushSelectionRange(jsDocCommentStart, end);
|
||||
}
|
||||
pushSelectionRange(start, end);
|
||||
|
||||
// String literals should have a stop both inside and outside their quotes.
|
||||
if (isStringLiteral(node) || isTemplateLiteral(node)) {
|
||||
pushSelectionRange(start + 1, end - 1);
|
||||
}
|
||||
|
||||
parentNode = node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return selectionRange;
|
||||
|
||||
function pushSelectionRange(start: number, end: number): void {
|
||||
// Skip empty ranges
|
||||
if (start !== end) {
|
||||
const textSpan = createTextSpanFromBounds(start, end);
|
||||
if (!selectionRange || (
|
||||
// Skip ranges that are identical to the parent
|
||||
!textSpansEqual(textSpan, selectionRange.textSpan) &&
|
||||
// Skip ranges that don’t contain the original position
|
||||
textSpanIntersectsWithPosition(textSpan, pos)
|
||||
)) {
|
||||
selectionRange = { textSpan, ...selectionRange && { parent: selectionRange } };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `ts.positionBelongsToNode`, except positions immediately after nodes
|
||||
* count too, unless that position belongs to the next node. In effect, makes
|
||||
* selections able to snap to preceding tokens when the cursor is on the tail
|
||||
* end of them with only whitespace ahead.
|
||||
* @param pos The position to check.
|
||||
* @param node The candidate node to snap to.
|
||||
* @param nextNode The next sibling node in the tree.
|
||||
* @param sourceFile The source file containing the nodes.
|
||||
*/
|
||||
function positionShouldSnapToNode(pos: number, node: Node, nextNode: Node | undefined) {
|
||||
// Can’t use 'ts.positionBelongsToNode()' here because it cleverly accounts
|
||||
// for missing nodes, which can’t really be considered when deciding what
|
||||
// to select.
|
||||
Debug.assert(node.pos <= pos);
|
||||
if (pos < node.end) {
|
||||
return true;
|
||||
}
|
||||
const nodeEnd = node.getEnd();
|
||||
const nextNodeStart = nextNode && nextNode.getStart();
|
||||
if (nodeEnd === pos) {
|
||||
return pos !== nextNodeStart;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const isImport = or(isImportDeclaration, isImportEqualsDeclaration);
|
||||
|
||||
/**
|
||||
* Gets the children of a node to be considered for selection ranging,
|
||||
* transforming them into an artificial tree according to their intuitive
|
||||
* grouping where no grouping actually exists in the parse tree. For example,
|
||||
* top-level imports are grouped into their own SyntaxList so they can be
|
||||
* selected all together, even though in the AST they’re just siblings of each
|
||||
* other as well as of other top-level statements and declarations.
|
||||
*/
|
||||
function getSelectionChildren(node: Node): ReadonlyArray<Node> {
|
||||
// Group top-level imports
|
||||
if (isSourceFile(node)) {
|
||||
return groupChildren(node.getChildAt(0).getChildren(), isImport);
|
||||
}
|
||||
|
||||
// Mapped types _look_ like ObjectTypes with a single member,
|
||||
// but in fact don’t contain a SyntaxList or a node containing
|
||||
// the “key/value” pair like ObjectTypes do, but it seems intuitive
|
||||
// that the selection would snap to those points. The philosophy
|
||||
// of choosing a selection range is not so much about what the
|
||||
// syntax currently _is_ as what the syntax might easily become
|
||||
// if the user is making a selection; e.g., we synthesize a selection
|
||||
// around the “key/value” pair not because there’s a node there, but
|
||||
// because it allows the mapped type to become an object type with a
|
||||
// few keystrokes.
|
||||
if (isMappedTypeNode(node)) {
|
||||
const [openBraceToken, ...children] = node.getChildren();
|
||||
const closeBraceToken = Debug.assertDefined(children.pop());
|
||||
Debug.assertEqual(openBraceToken.kind, SyntaxKind.OpenBraceToken);
|
||||
Debug.assertEqual(closeBraceToken.kind, SyntaxKind.CloseBraceToken);
|
||||
// Group `-/+readonly` and `-/+?`
|
||||
const groupedWithPlusMinusTokens = groupChildren(children, child =>
|
||||
child === node.readonlyToken || child.kind === SyntaxKind.ReadonlyKeyword ||
|
||||
child === node.questionToken || child.kind === SyntaxKind.QuestionToken);
|
||||
// Group type parameter with surrounding brackets
|
||||
const groupedWithBrackets = groupChildren(groupedWithPlusMinusTokens, ({ kind }) =>
|
||||
kind === SyntaxKind.OpenBracketToken ||
|
||||
kind === SyntaxKind.TypeParameter ||
|
||||
kind === SyntaxKind.CloseBracketToken
|
||||
);
|
||||
return [
|
||||
openBraceToken,
|
||||
// Pivot on `:`
|
||||
createSyntaxList(splitChildren(groupedWithBrackets, ({ kind }) => kind === SyntaxKind.ColonToken)),
|
||||
closeBraceToken,
|
||||
];
|
||||
}
|
||||
|
||||
// Group modifiers and property name, then pivot on `:`.
|
||||
if (isPropertySignature(node)) {
|
||||
const children = groupChildren(node.getChildren(), child =>
|
||||
child === node.name || contains(node.modifiers, child));
|
||||
return splitChildren(children, ({ kind }) => kind === SyntaxKind.ColonToken);
|
||||
}
|
||||
|
||||
// Group the parameter name with its `...`, then that group with its `?`, then pivot on `=`.
|
||||
if (isParameter(node)) {
|
||||
const groupedDotDotDotAndName = groupChildren(node.getChildren(), child =>
|
||||
child === node.dotDotDotToken || child === node.name);
|
||||
const groupedWithQuestionToken = groupChildren(groupedDotDotDotAndName, child =>
|
||||
child === groupedDotDotDotAndName[0] || child === node.questionToken);
|
||||
return splitChildren(groupedWithQuestionToken, ({ kind }) => kind === SyntaxKind.EqualsToken);
|
||||
}
|
||||
|
||||
// Pivot on '='
|
||||
if (isBindingElement(node)) {
|
||||
return splitChildren(node.getChildren(), ({ kind }) => kind === SyntaxKind.EqualsToken);
|
||||
}
|
||||
|
||||
return node.getChildren();
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups sibling nodes together into their own SyntaxList if they
|
||||
* a) are adjacent, AND b) match a predicate function.
|
||||
*/
|
||||
function groupChildren(children: Node[], groupOn: (child: Node) => boolean): Node[] {
|
||||
const result: Node[] = [];
|
||||
let group: Node[] | undefined;
|
||||
for (const child of children) {
|
||||
if (groupOn(child)) {
|
||||
group = group || [];
|
||||
group.push(child);
|
||||
}
|
||||
else {
|
||||
if (group) {
|
||||
result.push(createSyntaxList(group));
|
||||
group = undefined;
|
||||
}
|
||||
result.push(child);
|
||||
}
|
||||
}
|
||||
if (group) {
|
||||
result.push(createSyntaxList(group));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits sibling nodes into up to four partitions:
|
||||
* 1) everything left of the first node matched by `pivotOn`,
|
||||
* 2) the first node matched by `pivotOn`,
|
||||
* 3) everything right of the first node matched by `pivotOn`,
|
||||
* 4) a trailing semicolon, if `separateTrailingSemicolon` is enabled.
|
||||
* The left and right groups, if not empty, will each be grouped into their own containing SyntaxList.
|
||||
* @param children The sibling nodes to split.
|
||||
* @param pivotOn The predicate function to match the node to be the pivot. The first node that matches
|
||||
* the predicate will be used; any others that may match will be included into the right-hand group.
|
||||
* @param separateTrailingSemicolon If the last token is a semicolon, it will be returned as a separate
|
||||
* child rather than be included in the right-hand group.
|
||||
*/
|
||||
function splitChildren(children: Node[], pivotOn: (child: Node) => boolean, separateTrailingSemicolon = true): Node[] {
|
||||
if (children.length < 2) {
|
||||
return children;
|
||||
}
|
||||
const splitTokenIndex = findIndex(children, pivotOn);
|
||||
if (splitTokenIndex === -1) {
|
||||
return children;
|
||||
}
|
||||
const leftChildren = children.slice(0, splitTokenIndex);
|
||||
const splitToken = children[splitTokenIndex];
|
||||
const lastToken = last(children);
|
||||
const separateLastToken = separateTrailingSemicolon && lastToken.kind === SyntaxKind.SemicolonToken;
|
||||
const rightChildren = children.slice(splitTokenIndex + 1, separateLastToken ? children.length - 1 : undefined);
|
||||
const result = compact([
|
||||
leftChildren.length ? createSyntaxList(leftChildren) : undefined,
|
||||
splitToken,
|
||||
rightChildren.length ? createSyntaxList(rightChildren) : undefined,
|
||||
]);
|
||||
return separateLastToken ? result.concat(lastToken) : result;
|
||||
}
|
||||
|
||||
function createSyntaxList(children: Node[]): SyntaxList {
|
||||
Debug.assertGreaterThanOrEqual(children.length, 1);
|
||||
const syntaxList = createNode(SyntaxKind.SyntaxList, children[0].pos, last(children).end) as SyntaxList;
|
||||
syntaxList._children = children;
|
||||
return syntaxList;
|
||||
}
|
||||
|
||||
function isListOpener(token: Node | undefined): token is Node {
|
||||
const kind = token && token.kind;
|
||||
return kind === SyntaxKind.OpenBraceToken
|
||||
|| kind === SyntaxKind.OpenBracketToken
|
||||
|| kind === SyntaxKind.OpenParenToken
|
||||
|| kind === SyntaxKind.JsxOpeningElement;
|
||||
}
|
||||
|
||||
function isListCloser(token: Node | undefined): token is Node {
|
||||
const kind = token && token.kind;
|
||||
return kind === SyntaxKind.CloseBraceToken
|
||||
|| kind === SyntaxKind.CloseBracketToken
|
||||
|| kind === SyntaxKind.CloseParenToken
|
||||
|| kind === SyntaxKind.JsxClosingElement;
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,17 @@ namespace ts.Completions.StringCompletions {
|
||||
return convertPathCompletions(completion.paths);
|
||||
case StringLiteralCompletionKind.Properties: {
|
||||
const entries: CompletionEntry[] = [];
|
||||
getCompletionEntriesFromSymbols(completion.symbols, entries, sourceFile, sourceFile, checker, ScriptTarget.ESNext, log, CompletionKind.String, preferences); // Target will not be used, so arbitrary
|
||||
getCompletionEntriesFromSymbols(
|
||||
completion.symbols,
|
||||
entries,
|
||||
sourceFile,
|
||||
sourceFile,
|
||||
checker,
|
||||
ScriptTarget.ESNext,
|
||||
log,
|
||||
CompletionKind.String,
|
||||
preferences
|
||||
); // Target will not be used, so arbitrary
|
||||
return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: completion.hasIndexSignature, entries };
|
||||
}
|
||||
case StringLiteralCompletionKind.Types: {
|
||||
@@ -60,7 +70,7 @@ namespace ts.Completions.StringCompletions {
|
||||
const isGlobalCompletion = false; // We don't want the editor to offer any other completions, such as snippets, inside a comment.
|
||||
const isNewIdentifierLocation = true; // The user may type in a path that doesn't yet exist, creating a "new identifier" with respect to the collection of identifiers the server is aware of.
|
||||
const entries = pathCompletions.map(({ name, kind, span, extension }): CompletionEntry =>
|
||||
({ name, kind, kindModifiers: kindModifiersFromExtension(extension), sortText: "0", replacementSpan: span }));
|
||||
({ name, kind, kindModifiers: kindModifiersFromExtension(extension), sortText: SortText.LocationPriority, replacementSpan: span }));
|
||||
return { isGlobalCompletion, isMemberCompletion: false, isNewIdentifierLocation, entries };
|
||||
}
|
||||
function kindModifiersFromExtension(extension: Extension | undefined): ScriptElementKindModifier {
|
||||
|
||||
+143
-104
@@ -222,6 +222,12 @@ namespace ts.textChanges {
|
||||
|
||||
export type TypeAnnotatable = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertyDeclaration | PropertySignature;
|
||||
|
||||
export type ThisTypeAnnotatable = FunctionDeclaration | FunctionExpression;
|
||||
|
||||
export function isThisTypeAnnotatable(containingFunction: FunctionLike): containingFunction is ThisTypeAnnotatable {
|
||||
return isFunctionExpression(containingFunction) || isFunctionDeclaration(containingFunction);
|
||||
}
|
||||
|
||||
export class ChangeTracker {
|
||||
private readonly changes: Change[] = [];
|
||||
private readonly newFiles: { readonly oldFile: SourceFile | undefined, readonly fileName: string, readonly statements: ReadonlyArray<Statement> }[] = [];
|
||||
@@ -393,6 +399,13 @@ namespace ts.textChanges {
|
||||
this.insertNodeAt(sourceFile, endNode.end, type, { prefix: ": " });
|
||||
}
|
||||
|
||||
public tryInsertThisTypeAnnotation(sourceFile: SourceFile, node: ThisTypeAnnotatable, type: TypeNode): void {
|
||||
const start = findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile)!.getStart(sourceFile) + 1;
|
||||
const suffix = node.parameters.length ? ", " : "";
|
||||
|
||||
this.insertNodeAt(sourceFile, start, type, { prefix: "this: ", suffix });
|
||||
}
|
||||
|
||||
public insertTypeParameters(sourceFile: SourceFile, node: SignatureDeclaration, typeParameters: ReadonlyArray<TypeParameterDeclaration>): void {
|
||||
// If no `(`, is an arrow function `x => x`, so use the pos of the first parameter
|
||||
const start = (findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile) || first(node.parameters)).getStart(sourceFile);
|
||||
@@ -837,9 +850,10 @@ namespace ts.textChanges {
|
||||
|
||||
/** Note: output node may be mutated input node. */
|
||||
export function getNonformattedText(node: Node, sourceFile: SourceFile | undefined, newLineCharacter: string): { text: string, node: Node } {
|
||||
const writer = new Writer(newLineCharacter);
|
||||
const omitTrailingSemicolon = !!sourceFile && !probablyUsesSemicolons(sourceFile);
|
||||
const writer = createWriter(newLineCharacter, omitTrailingSemicolon);
|
||||
const newLine = newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed;
|
||||
createPrinter({ newLine, neverAsciiEscape: true }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer);
|
||||
createPrinter({ newLine, neverAsciiEscape: true, omitTrailingSemicolon }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer);
|
||||
return { text: writer.getText(), node: assignPositionsToNode(node) };
|
||||
}
|
||||
}
|
||||
@@ -877,143 +891,168 @@ namespace ts.textChanges {
|
||||
return nodeArray;
|
||||
}
|
||||
|
||||
class Writer implements EmitTextWriter, PrintHandlers {
|
||||
private lastNonTriviaPosition = 0;
|
||||
private readonly writer: EmitTextWriter;
|
||||
interface TextChangesWriter extends EmitTextWriter, PrintHandlers {}
|
||||
|
||||
public readonly onEmitNode: PrintHandlers["onEmitNode"];
|
||||
public readonly onBeforeEmitNodeArray: PrintHandlers["onBeforeEmitNodeArray"];
|
||||
public readonly onAfterEmitNodeArray: PrintHandlers["onAfterEmitNodeArray"];
|
||||
public readonly onBeforeEmitToken: PrintHandlers["onBeforeEmitToken"];
|
||||
public readonly onAfterEmitToken: PrintHandlers["onAfterEmitToken"];
|
||||
function createWriter(newLine: string, omitTrailingSemicolon?: boolean): TextChangesWriter {
|
||||
let lastNonTriviaPosition = 0;
|
||||
|
||||
constructor(newLine: string) {
|
||||
this.writer = createTextWriter(newLine);
|
||||
this.onEmitNode = (hint, node, printCallback) => {
|
||||
if (node) {
|
||||
setPos(node, this.lastNonTriviaPosition);
|
||||
}
|
||||
printCallback(hint, node);
|
||||
if (node) {
|
||||
setEnd(node, this.lastNonTriviaPosition);
|
||||
}
|
||||
};
|
||||
this.onBeforeEmitNodeArray = nodes => {
|
||||
if (nodes) {
|
||||
setPos(nodes, this.lastNonTriviaPosition);
|
||||
}
|
||||
};
|
||||
this.onAfterEmitNodeArray = nodes => {
|
||||
if (nodes) {
|
||||
setEnd(nodes, this.lastNonTriviaPosition);
|
||||
}
|
||||
};
|
||||
this.onBeforeEmitToken = node => {
|
||||
if (node) {
|
||||
setPos(node, this.lastNonTriviaPosition);
|
||||
}
|
||||
};
|
||||
this.onAfterEmitToken = node => {
|
||||
if (node) {
|
||||
setEnd(node, this.lastNonTriviaPosition);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private setLastNonTriviaPosition(s: string, force: boolean) {
|
||||
const writer = omitTrailingSemicolon ? getTrailingSemicolonOmittingWriter(createTextWriter(newLine)) : createTextWriter(newLine);
|
||||
const onEmitNode: PrintHandlers["onEmitNode"] = (hint, node, printCallback) => {
|
||||
if (node) {
|
||||
setPos(node, lastNonTriviaPosition);
|
||||
}
|
||||
printCallback(hint, node);
|
||||
if (node) {
|
||||
setEnd(node, lastNonTriviaPosition);
|
||||
}
|
||||
};
|
||||
const onBeforeEmitNodeArray: PrintHandlers["onBeforeEmitNodeArray"] = nodes => {
|
||||
if (nodes) {
|
||||
setPos(nodes, lastNonTriviaPosition);
|
||||
}
|
||||
};
|
||||
const onAfterEmitNodeArray: PrintHandlers["onAfterEmitNodeArray"] = nodes => {
|
||||
if (nodes) {
|
||||
setEnd(nodes, lastNonTriviaPosition);
|
||||
}
|
||||
};
|
||||
const onBeforeEmitToken: PrintHandlers["onBeforeEmitToken"] = node => {
|
||||
if (node) {
|
||||
setPos(node, lastNonTriviaPosition);
|
||||
}
|
||||
};
|
||||
const onAfterEmitToken: PrintHandlers["onAfterEmitToken"] = node => {
|
||||
if (node) {
|
||||
setEnd(node, lastNonTriviaPosition);
|
||||
}
|
||||
};
|
||||
|
||||
function setLastNonTriviaPosition(s: string, force: boolean) {
|
||||
if (force || !isTrivia(s)) {
|
||||
this.lastNonTriviaPosition = this.writer.getTextPos();
|
||||
lastNonTriviaPosition = writer.getTextPos();
|
||||
let i = 0;
|
||||
while (isWhiteSpaceLike(s.charCodeAt(s.length - i - 1))) {
|
||||
i++;
|
||||
}
|
||||
// trim trailing whitespaces
|
||||
this.lastNonTriviaPosition -= i;
|
||||
lastNonTriviaPosition -= i;
|
||||
}
|
||||
}
|
||||
|
||||
write(s: string): void {
|
||||
this.writer.write(s);
|
||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
||||
function write(s: string): void {
|
||||
writer.write(s);
|
||||
setLastNonTriviaPosition(s, /*force*/ false);
|
||||
}
|
||||
writeComment(s: string): void {
|
||||
this.writer.writeComment(s);
|
||||
function writeComment(s: string): void {
|
||||
writer.writeComment(s);
|
||||
}
|
||||
writeKeyword(s: string): void {
|
||||
this.writer.writeKeyword(s);
|
||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
||||
function writeKeyword(s: string): void {
|
||||
writer.writeKeyword(s);
|
||||
setLastNonTriviaPosition(s, /*force*/ false);
|
||||
}
|
||||
writeOperator(s: string): void {
|
||||
this.writer.writeOperator(s);
|
||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
||||
function writeOperator(s: string): void {
|
||||
writer.writeOperator(s);
|
||||
setLastNonTriviaPosition(s, /*force*/ false);
|
||||
}
|
||||
writePunctuation(s: string): void {
|
||||
this.writer.writePunctuation(s);
|
||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
||||
function writePunctuation(s: string): void {
|
||||
writer.writePunctuation(s);
|
||||
setLastNonTriviaPosition(s, /*force*/ false);
|
||||
}
|
||||
writeTrailingSemicolon(s: string): void {
|
||||
this.writer.writeTrailingSemicolon(s);
|
||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
||||
function writeTrailingSemicolon(s: string): void {
|
||||
writer.writeTrailingSemicolon(s);
|
||||
setLastNonTriviaPosition(s, /*force*/ false);
|
||||
}
|
||||
writeParameter(s: string): void {
|
||||
this.writer.writeParameter(s);
|
||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
||||
function writeParameter(s: string): void {
|
||||
writer.writeParameter(s);
|
||||
setLastNonTriviaPosition(s, /*force*/ false);
|
||||
}
|
||||
writeProperty(s: string): void {
|
||||
this.writer.writeProperty(s);
|
||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
||||
function writeProperty(s: string): void {
|
||||
writer.writeProperty(s);
|
||||
setLastNonTriviaPosition(s, /*force*/ false);
|
||||
}
|
||||
writeSpace(s: string): void {
|
||||
this.writer.writeSpace(s);
|
||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
||||
function writeSpace(s: string): void {
|
||||
writer.writeSpace(s);
|
||||
setLastNonTriviaPosition(s, /*force*/ false);
|
||||
}
|
||||
writeStringLiteral(s: string): void {
|
||||
this.writer.writeStringLiteral(s);
|
||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
||||
function writeStringLiteral(s: string): void {
|
||||
writer.writeStringLiteral(s);
|
||||
setLastNonTriviaPosition(s, /*force*/ false);
|
||||
}
|
||||
writeSymbol(s: string, sym: Symbol): void {
|
||||
this.writer.writeSymbol(s, sym);
|
||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
||||
function writeSymbol(s: string, sym: Symbol): void {
|
||||
writer.writeSymbol(s, sym);
|
||||
setLastNonTriviaPosition(s, /*force*/ false);
|
||||
}
|
||||
writeLine(): void {
|
||||
this.writer.writeLine();
|
||||
function writeLine(): void {
|
||||
writer.writeLine();
|
||||
}
|
||||
increaseIndent(): void {
|
||||
this.writer.increaseIndent();
|
||||
function increaseIndent(): void {
|
||||
writer.increaseIndent();
|
||||
}
|
||||
decreaseIndent(): void {
|
||||
this.writer.decreaseIndent();
|
||||
function decreaseIndent(): void {
|
||||
writer.decreaseIndent();
|
||||
}
|
||||
getText(): string {
|
||||
return this.writer.getText();
|
||||
function getText(): string {
|
||||
return writer.getText();
|
||||
}
|
||||
rawWrite(s: string): void {
|
||||
this.writer.rawWrite(s);
|
||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
||||
function rawWrite(s: string): void {
|
||||
writer.rawWrite(s);
|
||||
setLastNonTriviaPosition(s, /*force*/ false);
|
||||
}
|
||||
writeLiteral(s: string): void {
|
||||
this.writer.writeLiteral(s);
|
||||
this.setLastNonTriviaPosition(s, /*force*/ true);
|
||||
function writeLiteral(s: string): void {
|
||||
writer.writeLiteral(s);
|
||||
setLastNonTriviaPosition(s, /*force*/ true);
|
||||
}
|
||||
getTextPos(): number {
|
||||
return this.writer.getTextPos();
|
||||
function getTextPos(): number {
|
||||
return writer.getTextPos();
|
||||
}
|
||||
getLine(): number {
|
||||
return this.writer.getLine();
|
||||
function getLine(): number {
|
||||
return writer.getLine();
|
||||
}
|
||||
getColumn(): number {
|
||||
return this.writer.getColumn();
|
||||
function getColumn(): number {
|
||||
return writer.getColumn();
|
||||
}
|
||||
getIndent(): number {
|
||||
return this.writer.getIndent();
|
||||
function getIndent(): number {
|
||||
return writer.getIndent();
|
||||
}
|
||||
isAtStartOfLine(): boolean {
|
||||
return this.writer.isAtStartOfLine();
|
||||
function isAtStartOfLine(): boolean {
|
||||
return writer.isAtStartOfLine();
|
||||
}
|
||||
clear(): void {
|
||||
this.writer.clear();
|
||||
this.lastNonTriviaPosition = 0;
|
||||
function clear(): void {
|
||||
writer.clear();
|
||||
lastNonTriviaPosition = 0;
|
||||
}
|
||||
|
||||
return {
|
||||
onEmitNode,
|
||||
onBeforeEmitNodeArray,
|
||||
onAfterEmitNodeArray,
|
||||
onBeforeEmitToken,
|
||||
onAfterEmitToken,
|
||||
write,
|
||||
writeComment,
|
||||
writeKeyword,
|
||||
writeOperator,
|
||||
writePunctuation,
|
||||
writeTrailingSemicolon,
|
||||
writeParameter,
|
||||
writeProperty,
|
||||
writeSpace,
|
||||
writeStringLiteral,
|
||||
writeSymbol,
|
||||
writeLine,
|
||||
increaseIndent,
|
||||
decreaseIndent,
|
||||
getText,
|
||||
rawWrite,
|
||||
writeLiteral,
|
||||
getTextPos,
|
||||
getLine,
|
||||
getColumn,
|
||||
getIndent,
|
||||
isAtStartOfLine,
|
||||
clear
|
||||
};
|
||||
}
|
||||
|
||||
function getInsertionPositionAtSourceFileTop(sourceFile: SourceFile): number {
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"patternMatcher.ts",
|
||||
"preProcess.ts",
|
||||
"rename.ts",
|
||||
"smartSelection.ts",
|
||||
"signatureHelp.ts",
|
||||
"sourcemaps.ts",
|
||||
"suggestionDiagnostics.ts",
|
||||
|
||||
@@ -296,6 +296,8 @@ namespace ts {
|
||||
getRenameInfo(fileName: string, position: number, options?: RenameInfoOptions): RenameInfo;
|
||||
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): ReadonlyArray<RenameLocation> | undefined;
|
||||
|
||||
getSmartSelectionRange(fileName: string, position: number): SelectionRange;
|
||||
|
||||
getDefinitionAtPosition(fileName: string, position: number): ReadonlyArray<DefinitionInfo> | undefined;
|
||||
getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined;
|
||||
getTypeDefinitionAtPosition(fileName: string, position: number): ReadonlyArray<DefinitionInfo> | undefined;
|
||||
@@ -611,6 +613,13 @@ namespace ts {
|
||||
*/
|
||||
originalTextSpan?: TextSpan;
|
||||
originalFileName?: string;
|
||||
|
||||
/**
|
||||
* If DocumentSpan.textSpan is the span for name of the declaration,
|
||||
* then this is the span for relevant declaration
|
||||
*/
|
||||
contextSpan?: TextSpan;
|
||||
originalContextSpan?: TextSpan;
|
||||
}
|
||||
|
||||
export interface RenameLocation extends DocumentSpan {
|
||||
@@ -645,6 +654,7 @@ namespace ts {
|
||||
fileName?: string;
|
||||
isInString?: true;
|
||||
textSpan: TextSpan;
|
||||
contextSpan?: TextSpan;
|
||||
kind: HighlightSpanKind;
|
||||
}
|
||||
|
||||
@@ -848,6 +858,11 @@ namespace ts {
|
||||
isOptional: boolean;
|
||||
}
|
||||
|
||||
export interface SelectionRange {
|
||||
textSpan: TextSpan;
|
||||
parent?: SelectionRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single signature to show in signature help.
|
||||
* The id is used for subsequent calls into the language service to ask questions about the
|
||||
|
||||
@@ -1190,8 +1190,8 @@ namespace ts {
|
||||
return !!range && shouldBeReference === tripleSlashDirectivePrefixRegex.test(sourceFile.text.substring(range.pos, range.end));
|
||||
}
|
||||
|
||||
export function createTextSpanFromNode(node: Node, sourceFile?: SourceFile): TextSpan {
|
||||
return createTextSpanFromBounds(node.getStart(sourceFile), node.getEnd());
|
||||
export function createTextSpanFromNode(node: Node, sourceFile?: SourceFile, endNode?: Node): TextSpan {
|
||||
return createTextSpanFromBounds(node.getStart(sourceFile), (endNode || node).getEnd());
|
||||
}
|
||||
|
||||
export function createTextRangeFromNode(node: Node, sourceFile: SourceFile): TextRange {
|
||||
@@ -1991,4 +1991,51 @@ namespace ts {
|
||||
});
|
||||
return typeIsAccessible ? res : undefined;
|
||||
}
|
||||
|
||||
export function syntaxUsuallyHasTrailingSemicolon(kind: SyntaxKind) {
|
||||
return kind === SyntaxKind.VariableStatement
|
||||
|| kind === SyntaxKind.ExpressionStatement
|
||||
|| kind === SyntaxKind.DoStatement
|
||||
|| kind === SyntaxKind.ContinueStatement
|
||||
|| kind === SyntaxKind.BreakStatement
|
||||
|| kind === SyntaxKind.ReturnStatement
|
||||
|| kind === SyntaxKind.ThrowStatement
|
||||
|| kind === SyntaxKind.DebuggerStatement
|
||||
|| kind === SyntaxKind.PropertyDeclaration
|
||||
|| kind === SyntaxKind.TypeAliasDeclaration
|
||||
|| kind === SyntaxKind.ImportDeclaration
|
||||
|| kind === SyntaxKind.ImportEqualsDeclaration
|
||||
|| kind === SyntaxKind.ExportDeclaration;
|
||||
}
|
||||
|
||||
export function probablyUsesSemicolons(sourceFile: SourceFile): boolean {
|
||||
let withSemicolon = 0;
|
||||
let withoutSemicolon = 0;
|
||||
const nStatementsToObserve = 5;
|
||||
forEachChild(sourceFile, function visit(node): boolean | undefined {
|
||||
if (syntaxUsuallyHasTrailingSemicolon(node.kind)) {
|
||||
const lastToken = node.getLastToken(sourceFile);
|
||||
if (lastToken && lastToken.kind === SyntaxKind.SemicolonToken) {
|
||||
withSemicolon++;
|
||||
}
|
||||
else {
|
||||
withoutSemicolon++;
|
||||
}
|
||||
}
|
||||
if (withSemicolon + withoutSemicolon >= nStatementsToObserve) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return forEachChild(node, visit);
|
||||
});
|
||||
|
||||
// One statement missing a semicolon isn’t sufficient evidence to say the user
|
||||
// doesn’t want semicolons, because they may not even be done writing that statement.
|
||||
if (withSemicolon === 0 && withoutSemicolon <= 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If even 2/5 places have a semicolon, the user probably wants semicolons
|
||||
return withSemicolon / withoutSemicolon > 1 / nStatementsToObserve;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user