mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge branch 'master' into avoid-quickfix-for-declaration-file
This commit is contained in:
@@ -35,7 +35,7 @@ namespace ts.codefix {
|
||||
precedingNode = ctorDeclaration.parent.parent;
|
||||
newClassDeclaration = createClassFromVariableDeclaration(ctorDeclaration as VariableDeclaration);
|
||||
if ((<VariableDeclarationList>ctorDeclaration.parent).declarations.length === 1) {
|
||||
copyComments(precedingNode, newClassDeclaration!, sourceFile); // TODO: GH#18217
|
||||
copyLeadingComments(precedingNode, newClassDeclaration!, sourceFile); // TODO: GH#18217
|
||||
changes.delete(sourceFile, precedingNode);
|
||||
}
|
||||
else {
|
||||
@@ -48,7 +48,7 @@ namespace ts.codefix {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
copyComments(ctorDeclaration, newClassDeclaration, sourceFile);
|
||||
copyLeadingComments(ctorDeclaration, newClassDeclaration, sourceFile);
|
||||
|
||||
// Because the preceding node could be touched, we need to insert nodes before delete nodes.
|
||||
changes.insertNodeAfter(sourceFile, precedingNode!, newClassDeclaration);
|
||||
@@ -112,7 +112,7 @@ namespace ts.codefix {
|
||||
const fullModifiers = concatenate(modifiers, getModifierKindFromSource(functionExpression, SyntaxKind.AsyncKeyword));
|
||||
const method = createMethod(/*decorators*/ undefined, fullModifiers, /*asteriskToken*/ undefined, memberDeclaration.name, /*questionToken*/ undefined,
|
||||
/*typeParameters*/ undefined, functionExpression.parameters, /*type*/ undefined, functionExpression.body);
|
||||
copyComments(assignmentBinaryExpression, method, sourceFile);
|
||||
copyLeadingComments(assignmentBinaryExpression, method, sourceFile);
|
||||
return method;
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace ts.codefix {
|
||||
const fullModifiers = concatenate(modifiers, getModifierKindFromSource(arrowFunction, SyntaxKind.AsyncKeyword));
|
||||
const method = createMethod(/*decorators*/ undefined, fullModifiers, /*asteriskToken*/ undefined, memberDeclaration.name, /*questionToken*/ undefined,
|
||||
/*typeParameters*/ undefined, arrowFunction.parameters, /*type*/ undefined, bodyBlock);
|
||||
copyComments(assignmentBinaryExpression, method, sourceFile);
|
||||
copyLeadingComments(assignmentBinaryExpression, method, sourceFile);
|
||||
return method;
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ namespace ts.codefix {
|
||||
}
|
||||
const prop = createProperty(/*decorators*/ undefined, modifiers, memberDeclaration.name, /*questionToken*/ undefined,
|
||||
/*type*/ undefined, assignmentBinaryExpression.right);
|
||||
copyComments(assignmentBinaryExpression.parent, prop, sourceFile);
|
||||
copyLeadingComments(assignmentBinaryExpression.parent, prop, sourceFile);
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace ts {
|
||||
return textChanges.getNewFileText(toStatements(valueInfo, outputKind), ScriptKind.TS, formatSettings.newLineCharacter || "\n", formatting.getFormatContext(formatSettings));
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
const enum OutputKind { ExportEquals, NamedExport, NamespaceMember, Global }
|
||||
function toNamespaceMemberStatements(info: ValueInfo): ReadonlyArray<Statement> {
|
||||
return toStatements(info, OutputKind.NamespaceMember);
|
||||
|
||||
@@ -156,7 +156,7 @@ namespace ts.codefix {
|
||||
isIdentifier(arg) ? arg.text :
|
||||
isPropertyAccessExpression(arg) ? arg.name.text : undefined);
|
||||
const contextualType = checker.getContextualType(call);
|
||||
const returnType = inJs ? undefined : contextualType && checker.typeToTypeNode(contextualType, contextNode, /*flags*/ undefined, tracker) || createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
||||
const returnType = (inJs || !contextualType) ? undefined : checker.typeToTypeNode(contextualType, contextNode, /*flags*/ undefined, tracker);
|
||||
return createMethod(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ makeStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined,
|
||||
@@ -264,6 +264,7 @@ namespace ts.codefix {
|
||||
createNew(
|
||||
createIdentifier("Error"),
|
||||
/*typeArguments*/ undefined,
|
||||
// TODO Handle auto quote preference.
|
||||
[createLiteral("Method not implemented.", /*isSingleQuote*/ preferences.quotePreference === "single")]))],
|
||||
/*multiline*/ true);
|
||||
}
|
||||
|
||||
@@ -274,7 +274,17 @@ namespace ts.codefix {
|
||||
return !!merged;
|
||||
}));
|
||||
const tag = createJSDocComment(comments.join("\n"), createNodeArray([...(oldTags || emptyArray), ...unmergedNewTags]));
|
||||
changes.insertJsdocCommentBefore(sourceFile, parent, tag);
|
||||
const jsDocNode = parent.kind === SyntaxKind.ArrowFunction ? getJsDocNodeForArrowFunction(parent) : parent;
|
||||
jsDocNode.jsDoc = parent.jsDoc;
|
||||
jsDocNode.jsDocCache = parent.jsDocCache;
|
||||
changes.insertJsdocCommentBefore(sourceFile, jsDocNode, tag);
|
||||
}
|
||||
|
||||
function getJsDocNodeForArrowFunction(signature: ArrowFunction): HasJSDoc {
|
||||
if (signature.parent.kind === SyntaxKind.PropertyDeclaration) {
|
||||
return <HasJSDoc>signature.parent;
|
||||
}
|
||||
return <HasJSDoc>signature.parent.parent;
|
||||
}
|
||||
|
||||
function tryMergeJsdocTags(oldTag: JSDocTag, newTag: JSDocTag): JSDocTag | undefined {
|
||||
@@ -294,30 +304,6 @@ namespace ts.codefix {
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, program: Program, host: LanguageServiceHost): TypeNode | undefined {
|
||||
const checker = program.getTypeChecker();
|
||||
let typeIsAccessible = true;
|
||||
const notAccessible = () => { typeIsAccessible = false; };
|
||||
const res = checker.typeToTypeNode(type, enclosingScope, /*flags*/ undefined, {
|
||||
trackSymbol: (symbol, declaration, meaning) => {
|
||||
// TODO: GH#18217
|
||||
typeIsAccessible = typeIsAccessible && checker.isSymbolAccessible(symbol, declaration, meaning!, /*shouldComputeAliasToMarkVisible*/ false).accessibility === SymbolAccessibility.Accessible;
|
||||
},
|
||||
reportInaccessibleThisError: notAccessible,
|
||||
reportPrivateInBaseOfClassExpression: notAccessible,
|
||||
reportInaccessibleUniqueSymbolError: notAccessible,
|
||||
moduleResolverHost: {
|
||||
readFile: host.readFile,
|
||||
fileExists: host.fileExists,
|
||||
directoryExists: host.directoryExists,
|
||||
getSourceFiles: program.getSourceFiles,
|
||||
getCurrentDirectory: program.getCurrentDirectory,
|
||||
getCommonSourceDirectory: program.getCommonSourceDirectory,
|
||||
}
|
||||
});
|
||||
return typeIsAccessible ? res : undefined;
|
||||
}
|
||||
|
||||
function getReferences(token: PropertyName | Token<SyntaxKind.ConstructorKeyword>, program: Program, cancellationToken: CancellationToken): ReadonlyArray<Identifier> {
|
||||
// Position shouldn't matter since token is not a SourceFile.
|
||||
return mapDefined(FindAllReferences.getReferenceEntriesForNode(-1, token, program, program.getSourceFiles(), cancellationToken), entry =>
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace ts.Completions {
|
||||
ConstructorParameterKeywords, // Keywords at constructor parameter
|
||||
FunctionLikeBodyKeywords, // Keywords at function like body
|
||||
TypeKeywords,
|
||||
Last = TypeKeywords
|
||||
}
|
||||
|
||||
const enum GlobalsSearch { Continue, Success, Fail }
|
||||
@@ -77,7 +78,7 @@ 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 } = completionData;
|
||||
const { symbols, completionKind, isInSnippetScope, isNewIdentifierLocation, location, propertyAccessToConvert, keywordFilters, literals, symbolToOriginInfoMap, recommendedCompletion, isJsxInitializer, insideJsDocTagTypeExpression } = 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,
|
||||
@@ -113,7 +114,7 @@ namespace ts.Completions {
|
||||
|
||||
if (keywordFilters !== KeywordCompletionFilters.None) {
|
||||
const entryNames = arrayToSet(entries, e => e.name);
|
||||
for (const keywordEntry of getKeywordCompletions(keywordFilters)) {
|
||||
for (const keywordEntry of getKeywordCompletions(keywordFilters, !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile))) {
|
||||
if (!entryNames.has(keywordEntry.name)) {
|
||||
entries.push(keywordEntry);
|
||||
}
|
||||
@@ -510,6 +511,7 @@ namespace ts.Completions {
|
||||
readonly recommendedCompletion: Symbol | undefined;
|
||||
readonly previousToken: Node | undefined;
|
||||
readonly isJsxInitializer: IsJsxInitializer;
|
||||
readonly insideJsDocTagTypeExpression: boolean;
|
||||
}
|
||||
type Request = { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag } | { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag };
|
||||
|
||||
@@ -703,6 +705,14 @@ namespace ts.Completions {
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
propertyAccessToConvert = parent as PropertyAccessExpression;
|
||||
node = propertyAccessToConvert.expression;
|
||||
if (node.end === contextToken.pos &&
|
||||
isCallExpression(node) &&
|
||||
node.getChildCount(sourceFile) &&
|
||||
last(node.getChildren(sourceFile)).kind !== SyntaxKind.CloseParenToken) {
|
||||
// This is likely dot from incorrectly parsed call expression and user is starting to write spread
|
||||
// eg: Math.min(./**/)
|
||||
return undefined;
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.QualifiedName:
|
||||
node = (parent as QualifiedName).left;
|
||||
@@ -829,7 +839,22 @@ namespace ts.Completions {
|
||||
const literals = mapDefined(contextualType && (contextualType.isUnion() ? contextualType.types : [contextualType]), t => t.isLiteral() ? t.value : undefined);
|
||||
|
||||
const recommendedCompletion = previousToken && contextualType && getRecommendedCompletion(previousToken, contextualType, typeChecker);
|
||||
return { kind: CompletionDataKind.Data, symbols, completionKind, isInSnippetScope, propertyAccessToConvert, isNewIdentifierLocation, location, keywordFilters, literals, symbolToOriginInfoMap, recommendedCompletion, previousToken, isJsxInitializer };
|
||||
return {
|
||||
kind: CompletionDataKind.Data,
|
||||
symbols,
|
||||
completionKind,
|
||||
isInSnippetScope,
|
||||
propertyAccessToConvert,
|
||||
isNewIdentifierLocation,
|
||||
location,
|
||||
keywordFilters,
|
||||
literals,
|
||||
symbolToOriginInfoMap,
|
||||
recommendedCompletion,
|
||||
previousToken,
|
||||
isJsxInitializer,
|
||||
insideJsDocTagTypeExpression
|
||||
};
|
||||
|
||||
type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag;
|
||||
|
||||
@@ -882,7 +907,9 @@ namespace ts.Completions {
|
||||
}
|
||||
|
||||
// If the module is merged with a value, we must get the type of the class and add its propertes (for inherited static methods).
|
||||
if (!isTypeLocation && symbol.declarations.some(d => d.kind !== SyntaxKind.SourceFile && d.kind !== SyntaxKind.ModuleDeclaration && d.kind !== SyntaxKind.EnumDeclaration)) {
|
||||
if (!isTypeLocation &&
|
||||
symbol.declarations &&
|
||||
symbol.declarations.some(d => d.kind !== SyntaxKind.SourceFile && d.kind !== SyntaxKind.ModuleDeclaration && d.kind !== SyntaxKind.EnumDeclaration)) {
|
||||
addTypeProperties(typeChecker.getTypeOfSymbolAtLocation(symbol, node));
|
||||
}
|
||||
|
||||
@@ -1030,7 +1057,7 @@ namespace ts.Completions {
|
||||
|
||||
// Need to insert 'this.' before properties of `this` type, so only do that if `includeInsertTextCompletions`
|
||||
if (preferences.includeCompletionsWithInsertText && scopeNode.kind !== SyntaxKind.SourceFile) {
|
||||
const thisType = typeChecker.tryGetThisTypeAt(scopeNode);
|
||||
const thisType = typeChecker.tryGetThisTypeAt(scopeNode, /*includeGlobalThis*/ false);
|
||||
if (thisType) {
|
||||
for (const symbol of getPropertiesForCompletion(thisType, typeChecker)) {
|
||||
symbolToOriginInfoMap[getSymbolId(symbol)] = { kind: SymbolOriginInfoKind.ThisType };
|
||||
@@ -1129,6 +1156,9 @@ namespace ts.Completions {
|
||||
|
||||
case SyntaxKind.AsKeyword:
|
||||
return parentKind === SyntaxKind.AsExpression;
|
||||
|
||||
case SyntaxKind.ExtendsKeyword:
|
||||
return parentKind === SyntaxKind.TypeParameter;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -1916,7 +1946,18 @@ namespace ts.Completions {
|
||||
}
|
||||
return res;
|
||||
});
|
||||
function getKeywordCompletions(keywordFilter: KeywordCompletionFilters): ReadonlyArray<CompletionEntry> {
|
||||
|
||||
function getKeywordCompletions(keywordFilter: KeywordCompletionFilters, filterOutTsOnlyKeywords: boolean): ReadonlyArray<CompletionEntry> {
|
||||
if (!filterOutTsOnlyKeywords) return getTypescriptKeywordCompletions(keywordFilter);
|
||||
|
||||
const index = keywordFilter + KeywordCompletionFilters.Last + 1;
|
||||
return _keywordCompletions[index] ||
|
||||
(_keywordCompletions[index] = getTypescriptKeywordCompletions(keywordFilter)
|
||||
.filter(entry => !isTypeScriptOnlyKeyword(stringToToken(entry.name)!))
|
||||
);
|
||||
}
|
||||
|
||||
function getTypescriptKeywordCompletions(keywordFilter: KeywordCompletionFilters): ReadonlyArray<CompletionEntry> {
|
||||
return _keywordCompletions[keywordFilter] || (_keywordCompletions[keywordFilter] = allKeywordsCompletions().filter(entry => {
|
||||
const kind = stringToToken(entry.name)!;
|
||||
switch (keywordFilter) {
|
||||
@@ -1941,6 +1982,40 @@ namespace ts.Completions {
|
||||
}));
|
||||
}
|
||||
|
||||
function isTypeScriptOnlyKeyword(kind: SyntaxKind) {
|
||||
switch (kind) {
|
||||
case SyntaxKind.AbstractKeyword:
|
||||
case SyntaxKind.AnyKeyword:
|
||||
case SyntaxKind.BigIntKeyword:
|
||||
case SyntaxKind.BooleanKeyword:
|
||||
case SyntaxKind.DeclareKeyword:
|
||||
case SyntaxKind.EnumKeyword:
|
||||
case SyntaxKind.GlobalKeyword:
|
||||
case SyntaxKind.ImplementsKeyword:
|
||||
case SyntaxKind.InferKeyword:
|
||||
case SyntaxKind.InterfaceKeyword:
|
||||
case SyntaxKind.IsKeyword:
|
||||
case SyntaxKind.KeyOfKeyword:
|
||||
case SyntaxKind.ModuleKeyword:
|
||||
case SyntaxKind.NamespaceKeyword:
|
||||
case SyntaxKind.NeverKeyword:
|
||||
case SyntaxKind.NumberKeyword:
|
||||
case SyntaxKind.ObjectKeyword:
|
||||
case SyntaxKind.PrivateKeyword:
|
||||
case SyntaxKind.ProtectedKeyword:
|
||||
case SyntaxKind.PublicKeyword:
|
||||
case SyntaxKind.ReadonlyKeyword:
|
||||
case SyntaxKind.StringKeyword:
|
||||
case SyntaxKind.SymbolKeyword:
|
||||
case SyntaxKind.TypeKeyword:
|
||||
case SyntaxKind.UniqueKeyword:
|
||||
case SyntaxKind.UnknownKeyword:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isInterfaceOrTypeLiteralCompletionKeyword(kind: SyntaxKind): boolean {
|
||||
return kind === SyntaxKind.ReadonlyKeyword;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace ts.FindAllReferences {
|
||||
readonly implementations?: boolean;
|
||||
/**
|
||||
* True to opt in for enhanced renaming of shorthand properties and import/export specifiers.
|
||||
* The options controls the behavior for the whole rename operation; it cannot be changed on a per-file basis.
|
||||
* Default is false for backwards compatibility.
|
||||
*/
|
||||
readonly providePrefixAndSuffixTextForRename?: boolean;
|
||||
@@ -111,7 +112,7 @@ namespace ts.FindAllReferences {
|
||||
return flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options, sourceFilesSet));
|
||||
}
|
||||
|
||||
function flattenEntries(referenceSymbols: SymbolAndEntries[] | undefined): ReadonlyArray<Entry> | undefined {
|
||||
function flattenEntries(referenceSymbols: ReadonlyArray<SymbolAndEntries> | undefined): ReadonlyArray<Entry> | undefined {
|
||||
return referenceSymbols && flatMap(referenceSymbols, r => r.references);
|
||||
}
|
||||
|
||||
@@ -282,6 +283,11 @@ namespace ts.FindAllReferences {
|
||||
return createTextSpanFromBounds(start, end);
|
||||
}
|
||||
|
||||
export function getTextSpanOfEntry(entry: Entry) {
|
||||
return entry.kind === EntryKind.Span ? entry.textSpan :
|
||||
getTextSpan(entry.node, entry.node.getSourceFile());
|
||||
}
|
||||
|
||||
/** A node is considered a writeAccess iff it is a name of a declaration or a target of an assignment */
|
||||
function isWriteAccessForReference(node: Node): boolean {
|
||||
const decl = getDeclarationFromName(node);
|
||||
@@ -353,7 +359,7 @@ namespace ts.FindAllReferences {
|
||||
/* @internal */
|
||||
namespace ts.FindAllReferences.Core {
|
||||
/** Core find-all-references algorithm. Handles special cases before delegating to `getReferencedSymbolsForSymbol`. */
|
||||
export function getReferencedSymbolsForNode(position: number, node: Node, program: Program, sourceFiles: ReadonlyArray<SourceFile>, cancellationToken: CancellationToken, options: Options = {}, sourceFilesSet: ReadonlyMap<true> = arrayToSet(sourceFiles, f => f.fileName)): SymbolAndEntries[] | undefined {
|
||||
export function getReferencedSymbolsForNode(position: number, node: Node, program: Program, sourceFiles: ReadonlyArray<SourceFile>, cancellationToken: CancellationToken, options: Options = {}, sourceFilesSet: ReadonlyMap<true> = arrayToSet(sourceFiles, f => f.fileName)): ReadonlyArray<SymbolAndEntries> | undefined {
|
||||
if (isSourceFile(node)) {
|
||||
const reference = GoToDefinition.getReferenceAtPosition(node, position, program);
|
||||
const moduleSymbol = reference && program.getTypeChecker().getMergedSymbol(reference.file.symbol);
|
||||
@@ -368,7 +374,7 @@ namespace ts.FindAllReferences.Core {
|
||||
}
|
||||
|
||||
const checker = program.getTypeChecker();
|
||||
let symbol = checker.getSymbolAtLocation(node);
|
||||
const symbol = checker.getSymbolAtLocation(node);
|
||||
|
||||
// Could not find a symbol e.g. unknown identifier
|
||||
if (!symbol) {
|
||||
@@ -380,23 +386,95 @@ namespace ts.FindAllReferences.Core {
|
||||
return getReferencedSymbolsForModule(program, symbol.parent!, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet);
|
||||
}
|
||||
|
||||
let moduleReferences: SymbolAndEntries[] = emptyArray;
|
||||
const moduleSourceFile = isModuleSymbol(symbol);
|
||||
let referencedNode: Node | undefined = node;
|
||||
if (moduleSourceFile) {
|
||||
const exportEquals = symbol.exports!.get(InternalSymbolName.ExportEquals);
|
||||
// If !!exportEquals, we're about to add references to `import("mod")` anyway, so don't double-count them.
|
||||
moduleReferences = getReferencedSymbolsForModule(program, symbol, !!exportEquals, sourceFiles, sourceFilesSet);
|
||||
if (!exportEquals || !sourceFilesSet.has(moduleSourceFile.fileName)) return moduleReferences;
|
||||
// Continue to get references to 'export ='.
|
||||
symbol = skipAlias(exportEquals, checker);
|
||||
referencedNode = undefined;
|
||||
const moduleReferences = getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol, program, sourceFiles, cancellationToken, options, sourceFilesSet);
|
||||
if (moduleReferences && !(symbol.flags & SymbolFlags.Transient)) {
|
||||
return moduleReferences;
|
||||
}
|
||||
return concatenate(moduleReferences, getReferencedSymbolsForSymbol(symbol, referencedNode, sourceFiles, sourceFilesSet, checker, cancellationToken, options));
|
||||
|
||||
const aliasedSymbol = getMergedAliasedSymbolOfNamespaceExportDeclaration(node, symbol, checker);
|
||||
const moduleReferencesOfExportTarget = aliasedSymbol &&
|
||||
getReferencedSymbolsForModuleIfDeclaredBySourceFile(aliasedSymbol, program, sourceFiles, cancellationToken, options, sourceFilesSet);
|
||||
|
||||
const references = getReferencedSymbolsForSymbol(symbol, node, sourceFiles, sourceFilesSet, checker, cancellationToken, options);
|
||||
return mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget);
|
||||
}
|
||||
|
||||
function isModuleSymbol(symbol: Symbol): SourceFile | undefined {
|
||||
return symbol.flags & SymbolFlags.Module ? find(symbol.declarations, isSourceFile) : undefined;
|
||||
function getMergedAliasedSymbolOfNamespaceExportDeclaration(node: Node, symbol: Symbol, checker: TypeChecker) {
|
||||
if (node.parent && isNamespaceExportDeclaration(node.parent)) {
|
||||
const aliasedSymbol = checker.getAliasedSymbol(symbol);
|
||||
const targetSymbol = checker.getMergedSymbol(aliasedSymbol);
|
||||
if (aliasedSymbol !== targetSymbol) {
|
||||
return targetSymbol;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol: Symbol, program: Program, sourceFiles: ReadonlyArray<SourceFile>, cancellationToken: CancellationToken, options: Options, sourceFilesSet: ReadonlyMap<true>) {
|
||||
const moduleSourceFile = symbol.flags & SymbolFlags.Module ? find(symbol.declarations, isSourceFile) : undefined;
|
||||
if (!moduleSourceFile) return undefined;
|
||||
const exportEquals = symbol.exports!.get(InternalSymbolName.ExportEquals);
|
||||
// If !!exportEquals, we're about to add references to `import("mod")` anyway, so don't double-count them.
|
||||
const moduleReferences = getReferencedSymbolsForModule(program, symbol, !!exportEquals, sourceFiles, sourceFilesSet);
|
||||
if (!exportEquals || !sourceFilesSet.has(moduleSourceFile.fileName)) return moduleReferences;
|
||||
// Continue to get references to 'export ='.
|
||||
const checker = program.getTypeChecker();
|
||||
symbol = skipAlias(exportEquals, checker);
|
||||
return mergeReferences(program, moduleReferences, getReferencedSymbolsForSymbol(symbol, /*node*/ undefined, sourceFiles, sourceFilesSet, checker, cancellationToken, options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the references by sorting them (by file index in sourceFiles and their location in it) that point to same definition symbol
|
||||
*/
|
||||
function mergeReferences(program: Program, ...referencesToMerge: (SymbolAndEntries[] | undefined)[]): SymbolAndEntries[] | undefined {
|
||||
let result: SymbolAndEntries[] | undefined;
|
||||
for (const references of referencesToMerge) {
|
||||
if (!references || !references.length) continue;
|
||||
if (!result) {
|
||||
result = references;
|
||||
continue;
|
||||
}
|
||||
for (const entry of references) {
|
||||
if (!entry.definition || entry.definition.type !== DefinitionKind.Symbol) {
|
||||
result.push(entry);
|
||||
continue;
|
||||
}
|
||||
const symbol = entry.definition.symbol;
|
||||
const refIndex = findIndex(result, ref => !!ref.definition &&
|
||||
ref.definition.type === DefinitionKind.Symbol &&
|
||||
ref.definition.symbol === symbol);
|
||||
if (refIndex === -1) {
|
||||
result.push(entry);
|
||||
continue;
|
||||
}
|
||||
|
||||
const reference = result[refIndex];
|
||||
result[refIndex] = {
|
||||
definition: reference.definition,
|
||||
references: reference.references.concat(entry.references).sort((entry1, entry2) => {
|
||||
const entry1File = getSourceFileIndexOfEntry(program, entry1);
|
||||
const entry2File = getSourceFileIndexOfEntry(program, entry2);
|
||||
if (entry1File !== entry2File) {
|
||||
return compareValues(entry1File, entry2File);
|
||||
}
|
||||
|
||||
const entry1Span = getTextSpanOfEntry(entry1);
|
||||
const entry2Span = getTextSpanOfEntry(entry2);
|
||||
return entry1Span.start !== entry2Span.start ?
|
||||
compareValues(entry1Span.start, entry2Span.start) :
|
||||
compareValues(entry1Span.length, entry2Span.length);
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function getSourceFileIndexOfEntry(program: Program, entry: Entry) {
|
||||
const sourceFile = entry.kind === EntryKind.Span ?
|
||||
program.getSourceFile(entry.fileName)! :
|
||||
entry.node.getSourceFile();
|
||||
return program.getSourceFiles().indexOf(sourceFile);
|
||||
}
|
||||
|
||||
function getReferencedSymbolsForModule(program: Program, symbol: Symbol, excludeImportTypeOfExportEquals: boolean, sourceFiles: ReadonlyArray<SourceFile>, sourceFilesSet: ReadonlyMap<true>): SymbolAndEntries[] {
|
||||
@@ -435,7 +513,7 @@ namespace ts.FindAllReferences.Core {
|
||||
break;
|
||||
default:
|
||||
// This may be merged with something.
|
||||
Debug.fail("Expected a module symbol to be declared by a SourceFile or ModuleDeclaration.");
|
||||
Debug.assert(!!(symbol.flags & SymbolFlags.Transient), "Expected a module symbol to be declared by a SourceFile or ModuleDeclaration.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,25 +586,28 @@ namespace ts.FindAllReferences.Core {
|
||||
}
|
||||
else {
|
||||
const search = state.createSearch(node, symbol, /*comingFrom*/ undefined, { allSearchSymbols: node ? populateSearchSymbolSet(symbol, node, checker, !!options.isForRename, !!options.providePrefixAndSuffixTextForRename, !!options.implementations) : [symbol] });
|
||||
|
||||
// Try to get the smallest valid scope that we can limit our search to;
|
||||
// otherwise we'll need to search globally (i.e. include each file).
|
||||
const scope = getSymbolScope(symbol);
|
||||
if (scope) {
|
||||
getReferencesInContainer(scope, scope.getSourceFile(), search, state, /*addReferencesHere*/ !(isSourceFile(scope) && !contains(sourceFiles, scope)));
|
||||
}
|
||||
else {
|
||||
// Global search
|
||||
for (const sourceFile of state.sourceFiles) {
|
||||
state.cancellationToken.throwIfCancellationRequested();
|
||||
searchForName(sourceFile, search, state);
|
||||
}
|
||||
}
|
||||
getReferencesInContainerOrFiles(symbol, state, search);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function getReferencesInContainerOrFiles(symbol: Symbol, state: State, search: Search): void {
|
||||
// Try to get the smallest valid scope that we can limit our search to;
|
||||
// otherwise we'll need to search globally (i.e. include each file).
|
||||
const scope = getSymbolScope(symbol);
|
||||
if (scope) {
|
||||
getReferencesInContainer(scope, scope.getSourceFile(), search, state, /*addReferencesHere*/ !(isSourceFile(scope) && !contains(state.sourceFiles, scope)));
|
||||
}
|
||||
else {
|
||||
// Global search
|
||||
for (const sourceFile of state.sourceFiles) {
|
||||
state.cancellationToken.throwIfCancellationRequested();
|
||||
searchForName(sourceFile, search, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getSpecialSearchKind(node: Node): SpecialSearchKind {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.ConstructorKeyword:
|
||||
@@ -551,6 +632,8 @@ namespace ts.FindAllReferences.Core {
|
||||
// If the symbol is declared as part of a declaration like `{ type: "a" } | { type: "b" }`, use the property on the union type to get more references.
|
||||
return firstDefined(symbol.declarations, decl => {
|
||||
if (!decl.parent) {
|
||||
// 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)}`);
|
||||
}
|
||||
@@ -588,6 +671,12 @@ namespace ts.FindAllReferences.Core {
|
||||
Class,
|
||||
}
|
||||
|
||||
function getNonModuleSymbolOfMergedModuleSymbol(symbol: Symbol) {
|
||||
if (!(symbol.flags & (SymbolFlags.Module | SymbolFlags.Transient))) return undefined;
|
||||
const decl = symbol.declarations && find(symbol.declarations, d => !isSourceFile(d) && !isModuleDeclaration(d));
|
||||
return decl && decl.symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds all state needed for the finding references.
|
||||
* Unlike `Search`, there is only one `State`.
|
||||
@@ -621,7 +710,6 @@ namespace ts.FindAllReferences.Core {
|
||||
constructor(
|
||||
readonly sourceFiles: ReadonlyArray<SourceFile>,
|
||||
readonly sourceFilesSet: ReadonlyMap<true>,
|
||||
/** True if we're searching for constructor references. */
|
||||
readonly specialSearchKind: SpecialSearchKind,
|
||||
readonly checker: TypeChecker,
|
||||
readonly cancellationToken: CancellationToken,
|
||||
@@ -648,7 +736,7 @@ namespace ts.FindAllReferences.Core {
|
||||
// The other two forms seem to be handled downstream (e.g. in `skipPastExportOrImportSpecifier`), so special-casing the first form
|
||||
// here appears to be intentional).
|
||||
const {
|
||||
text = stripQuotes(unescapeLeadingUnderscores((getLocalSymbolForExportDefault(symbol) || symbol).escapedName)),
|
||||
text = stripQuotes(unescapeLeadingUnderscores((getLocalSymbolForExportDefault(symbol) || getNonModuleSymbolOfMergedModuleSymbol(symbol) || symbol).escapedName)),
|
||||
allSearchSymbols = [symbol],
|
||||
} = searchOptions;
|
||||
const escapedText = escapeLeadingUnderscores(text);
|
||||
@@ -1110,7 +1198,9 @@ namespace ts.FindAllReferences.Core {
|
||||
|
||||
// For `export { foo as bar }`, rename `foo`, but not `bar`.
|
||||
if (!isForRenameWithPrefixAndSuffixText(state.options) || alwaysGetReferences) {
|
||||
const exportKind = referenceLocation.originalKeywordKind === SyntaxKind.DefaultKeyword ? ExportKind.Default : ExportKind.Named;
|
||||
const isDefaultExport = referenceLocation.originalKeywordKind === SyntaxKind.DefaultKeyword
|
||||
|| exportSpecifier.name.originalKeywordKind === SyntaxKind.DefaultKeyword;
|
||||
const exportKind = isDefaultExport ? ExportKind.Default : ExportKind.Named;
|
||||
const exportSymbol = Debug.assertDefined(exportSpecifier.symbol);
|
||||
const exportInfo = Debug.assertDefined(getExportInfo(exportSymbol, exportKind, state.checker));
|
||||
searchForImportsOfExport(referenceLocation, exportSymbol, exportInfo, state);
|
||||
@@ -1205,6 +1295,7 @@ namespace ts.FindAllReferences.Core {
|
||||
const classExtending = tryGetClassByExtendingIdentifier(referenceLocation);
|
||||
if (classExtending) {
|
||||
findSuperConstructorAccesses(classExtending, pusher());
|
||||
findInheritedConstructorReferences(classExtending, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1237,35 +1328,44 @@ namespace ts.FindAllReferences.Core {
|
||||
* Reference the constructor and all calls to `new this()`.
|
||||
*/
|
||||
function findOwnConstructorReferences(classSymbol: Symbol, sourceFile: SourceFile, addNode: (node: Node) => void): void {
|
||||
for (const decl of classSymbol.members!.get(InternalSymbolName.Constructor)!.declarations) {
|
||||
const ctrKeyword = findChildOfKind(decl, SyntaxKind.ConstructorKeyword, sourceFile)!;
|
||||
Debug.assert(decl.kind === SyntaxKind.Constructor && !!ctrKeyword);
|
||||
addNode(ctrKeyword);
|
||||
const constructorSymbol = getClassConstructorSymbol(classSymbol);
|
||||
if (constructorSymbol) {
|
||||
for (const decl of constructorSymbol.declarations) {
|
||||
const ctrKeyword = findChildOfKind(decl, SyntaxKind.ConstructorKeyword, sourceFile)!;
|
||||
Debug.assert(decl.kind === SyntaxKind.Constructor && !!ctrKeyword);
|
||||
addNode(ctrKeyword);
|
||||
}
|
||||
}
|
||||
|
||||
classSymbol.exports!.forEach(member => {
|
||||
const decl = member.valueDeclaration;
|
||||
if (decl && decl.kind === SyntaxKind.MethodDeclaration) {
|
||||
const body = (<MethodDeclaration>decl).body;
|
||||
if (body) {
|
||||
forEachDescendantOfKind(body, SyntaxKind.ThisKeyword, thisKeyword => {
|
||||
if (isNewExpressionTarget(thisKeyword)) {
|
||||
addNode(thisKeyword);
|
||||
}
|
||||
});
|
||||
if (classSymbol.exports) {
|
||||
classSymbol.exports.forEach(member => {
|
||||
const decl = member.valueDeclaration;
|
||||
if (decl && decl.kind === SyntaxKind.MethodDeclaration) {
|
||||
const body = (<MethodDeclaration>decl).body;
|
||||
if (body) {
|
||||
forEachDescendantOfKind(body, SyntaxKind.ThisKeyword, thisKeyword => {
|
||||
if (isNewExpressionTarget(thisKeyword)) {
|
||||
addNode(thisKeyword);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getClassConstructorSymbol(classSymbol: Symbol): Symbol | undefined {
|
||||
return classSymbol.members && classSymbol.members.get(InternalSymbolName.Constructor);
|
||||
}
|
||||
|
||||
/** Find references to `super` in the constructor of an extending class. */
|
||||
function findSuperConstructorAccesses(cls: ClassLikeDeclaration, addNode: (node: Node) => void): void {
|
||||
const ctr = cls.symbol.members!.get(InternalSymbolName.Constructor);
|
||||
if (!ctr) {
|
||||
function findSuperConstructorAccesses(classDeclaration: ClassLikeDeclaration, addNode: (node: Node) => void): void {
|
||||
const constructor = getClassConstructorSymbol(classDeclaration.symbol);
|
||||
if (!constructor) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const decl of ctr.declarations) {
|
||||
for (const decl of constructor.declarations) {
|
||||
Debug.assert(decl.kind === SyntaxKind.Constructor);
|
||||
const body = (<ConstructorDeclaration>decl).body;
|
||||
if (body) {
|
||||
@@ -1278,6 +1378,17 @@ namespace ts.FindAllReferences.Core {
|
||||
}
|
||||
}
|
||||
|
||||
function hasOwnConstructor(classDeclaration: ClassLikeDeclaration): boolean {
|
||||
return !!getClassConstructorSymbol(classDeclaration.symbol);
|
||||
}
|
||||
|
||||
function findInheritedConstructorReferences(classDeclaration: ClassLikeDeclaration, state: State): void {
|
||||
if (hasOwnConstructor(classDeclaration)) return;
|
||||
const classSymbol = classDeclaration.symbol;
|
||||
const search = state.createSearch(/*location*/ undefined, classSymbol, /*comingFrom*/ undefined);
|
||||
getReferencesInContainerOrFiles(classSymbol, state, search);
|
||||
}
|
||||
|
||||
function addImplementationReferences(refNode: Node, addReference: (node: Node) => void, state: State): void {
|
||||
// Check if we found a function/propertyAssignment/method with an implementation or initializer
|
||||
if (isDeclarationName(refNode) && isImplementation(refNode.parent)) {
|
||||
@@ -1573,6 +1684,13 @@ namespace ts.FindAllReferences.Core {
|
||||
if (res2) return res2;
|
||||
}
|
||||
|
||||
const aliasedSymbol = getMergedAliasedSymbolOfNamespaceExportDeclaration(location, symbol, checker);
|
||||
if (aliasedSymbol) {
|
||||
// In case of UMD module and global merging, search for global as well
|
||||
const res = cbSymbol(aliasedSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.Node);
|
||||
if (res) return res;
|
||||
}
|
||||
|
||||
const res = fromRoot(symbol);
|
||||
if (res) return res;
|
||||
|
||||
|
||||
@@ -442,7 +442,7 @@ namespace ts.formatting {
|
||||
case SyntaxKind.ForInStatement:
|
||||
// "in" keyword in [P in keyof T]: T[P]
|
||||
case SyntaxKind.TypeParameter:
|
||||
return context.currentTokenSpan.kind === SyntaxKind.InKeyword || context.nextTokenSpan.kind === SyntaxKind.InKeyword;
|
||||
return context.currentTokenSpan.kind === SyntaxKind.InKeyword || context.nextTokenSpan.kind === SyntaxKind.InKeyword || context.currentTokenSpan.kind === SyntaxKind.EqualsToken || context.nextTokenSpan.kind === SyntaxKind.EqualsToken;
|
||||
// Technically, "of" is not a binary operator, but format it the same way as "in"
|
||||
case SyntaxKind.ForOfStatement:
|
||||
return context.currentTokenSpan.kind === SyntaxKind.OfKeyword || context.nextTokenSpan.kind === SyntaxKind.OfKeyword;
|
||||
|
||||
@@ -269,7 +269,7 @@ namespace ts.FindAllReferences {
|
||||
}
|
||||
|
||||
/**
|
||||
* `import x = require("./x") or `import * as x from "./x"`.
|
||||
* `import x = require("./x")` or `import * as x from "./x"`.
|
||||
* An `export =` may be imported by this syntax, so it may be a direct import.
|
||||
* If it's not a direct import, it will be in `indirectUsers`, so we don't have to do anything here.
|
||||
*/
|
||||
|
||||
@@ -68,8 +68,8 @@ namespace ts.OrganizeImports {
|
||||
else {
|
||||
// Note: Delete the surrounding trivia because it will have been retained in newImportDecls.
|
||||
changeTracker.replaceNodeWithNodes(sourceFile, oldImportDecls[0], newImportDecls, {
|
||||
useNonAdjustedStartPosition: true, // Leave header comment in place
|
||||
useNonAdjustedEndPosition: false,
|
||||
leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, // Leave header comment in place
|
||||
trailingTriviaOption: textChanges.TrailingTriviaOption.Include,
|
||||
suffix: getNewLineOrDefaultFromHost(host, formatContext.options),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -48,13 +48,13 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction {
|
||||
const returnStatement = createReturn(expression);
|
||||
body = createBlock([returnStatement], /* multiLine */ true);
|
||||
suppressLeadingAndTrailingTrivia(body);
|
||||
copyComments(expression!, returnStatement, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ true);
|
||||
copyLeadingComments(expression!, returnStatement, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ true);
|
||||
}
|
||||
else if (actionName === removeBracesActionName && returnStatement) {
|
||||
const actualExpression = expression || createVoidZero();
|
||||
body = needsParentheses(actualExpression) ? createParen(actualExpression) : actualExpression;
|
||||
suppressLeadingAndTrailingTrivia(body);
|
||||
copyComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false);
|
||||
copyLeadingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false);
|
||||
}
|
||||
else {
|
||||
Debug.fail("invalid action");
|
||||
|
||||
@@ -0,0 +1,579 @@
|
||||
/* @internal */
|
||||
namespace ts.refactor.convertParamsToDestructuredObject {
|
||||
const refactorName = "Convert parameters to destructured object";
|
||||
const minimumParameterLength = 2;
|
||||
registerRefactor(refactorName, { getEditsForAction, getAvailableActions });
|
||||
|
||||
|
||||
function getAvailableActions(context: RefactorContext): ReadonlyArray<ApplicableRefactorInfo> {
|
||||
const { file, startPosition } = context;
|
||||
const isJSFile = isSourceFileJS(file);
|
||||
if (isJSFile) return emptyArray; // TODO: GH#30113
|
||||
const functionDeclaration = getFunctionDeclarationAtPosition(file, startPosition, context.program.getTypeChecker());
|
||||
if (!functionDeclaration) return emptyArray;
|
||||
|
||||
const description = getLocaleSpecificMessage(Diagnostics.Convert_parameters_to_destructured_object);
|
||||
return [{
|
||||
name: refactorName,
|
||||
description,
|
||||
actions: [{
|
||||
name: refactorName,
|
||||
description
|
||||
}]
|
||||
}];
|
||||
}
|
||||
|
||||
function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined {
|
||||
Debug.assert(actionName === refactorName);
|
||||
const { file, startPosition, program, cancellationToken, host } = context;
|
||||
const functionDeclaration = getFunctionDeclarationAtPosition(file, startPosition, program.getTypeChecker());
|
||||
if (!functionDeclaration || !cancellationToken) return undefined;
|
||||
|
||||
const groupedReferences = getGroupedReferences(functionDeclaration, program, cancellationToken);
|
||||
if (groupedReferences.valid) {
|
||||
const edits = textChanges.ChangeTracker.with(context, t => doChange(file, program, host, t, functionDeclaration, groupedReferences));
|
||||
return { renameFilename: undefined, renameLocation: undefined, edits };
|
||||
}
|
||||
|
||||
return { edits: [] }; // TODO: GH#30113
|
||||
}
|
||||
|
||||
function doChange(
|
||||
sourceFile: SourceFile,
|
||||
program: Program,
|
||||
host: LanguageServiceHost,
|
||||
changes: textChanges.ChangeTracker,
|
||||
functionDeclaration: ValidFunctionDeclaration,
|
||||
groupedReferences: GroupedReferences): void {
|
||||
const newParamDeclaration = map(createNewParameters(functionDeclaration, program, host), param => getSynthesizedDeepClone(param));
|
||||
changes.replaceNodeRangeWithNodes(
|
||||
sourceFile,
|
||||
first(functionDeclaration.parameters),
|
||||
last(functionDeclaration.parameters),
|
||||
newParamDeclaration,
|
||||
{ joiner: ", ",
|
||||
// indentation is set to 0 because otherwise the object parameter will be indented if there is a `this` parameter
|
||||
indentation: 0,
|
||||
leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll,
|
||||
trailingTriviaOption: textChanges.TrailingTriviaOption.Include
|
||||
});
|
||||
|
||||
const functionCalls = sortAndDeduplicate(groupedReferences.functionCalls, /*comparer*/ (a, b) => compareValues(a.pos, b.pos));
|
||||
for (const call of functionCalls) {
|
||||
if (call.arguments && call.arguments.length) {
|
||||
const newArgument = getSynthesizedDeepClone(createNewArgument(functionDeclaration, call.arguments), /*includeTrivia*/ true);
|
||||
changes.replaceNodeRange(
|
||||
getSourceFileOfNode(call),
|
||||
first(call.arguments),
|
||||
last(call.arguments),
|
||||
newArgument,
|
||||
{ leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll, trailingTriviaOption: textChanges.TrailingTriviaOption.Include });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getGroupedReferences(functionDeclaration: ValidFunctionDeclaration, program: Program, cancellationToken: CancellationToken): GroupedReferences {
|
||||
const functionNames = getFunctionNames(functionDeclaration);
|
||||
const classNames = isConstructorDeclaration(functionDeclaration) ? getClassNames(functionDeclaration) : [];
|
||||
const names = deduplicate([...functionNames, ...classNames], equateValues);
|
||||
const checker = program.getTypeChecker();
|
||||
|
||||
const references = flatMap(names, /*mapfn*/ name => FindAllReferences.getReferenceEntriesForNode(-1, name, program, program.getSourceFiles(), cancellationToken));
|
||||
const groupedReferences = groupReferences(references);
|
||||
|
||||
if (!every(groupedReferences.declarations, /*callback*/ decl => contains(names, decl))) {
|
||||
groupedReferences.valid = false;
|
||||
}
|
||||
|
||||
return groupedReferences;
|
||||
|
||||
function groupReferences(referenceEntries: ReadonlyArray<FindAllReferences.Entry>): GroupedReferences {
|
||||
const classReferences: ClassReferences = { accessExpressions: [], typeUsages: [] };
|
||||
const groupedReferences: GroupedReferences = { functionCalls: [], declarations: [], classReferences, valid: true };
|
||||
const functionSymbols = map(functionNames, checker.getSymbolAtLocation);
|
||||
const classSymbols = map(classNames, checker.getSymbolAtLocation);
|
||||
const isConstructor = isConstructorDeclaration(functionDeclaration);
|
||||
|
||||
for (const entry of referenceEntries) {
|
||||
if (entry.kind !== FindAllReferences.EntryKind.Node) {
|
||||
groupedReferences.valid = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We compare symbols because in some cases find all references wil return a reference that may or may not be to the refactored function.
|
||||
Example from the refactorConvertParamsToDestructuredObject_methodCallUnion.ts test:
|
||||
class A { foo(a: number, b: number) { return a + b; } }
|
||||
class B { foo(c: number, d: number) { return c + d; } }
|
||||
declare const ab: A | B;
|
||||
ab.foo(1, 2);
|
||||
Find all references will return `ab.foo(1, 2)` as a reference to A's `foo` but we could be calling B's `foo`.
|
||||
When looking for constructor calls, however, the symbol on the constructor call reference is going to be the corresponding class symbol.
|
||||
So we need to add a special case for this because when calling a constructor of a class through one of its subclasses,
|
||||
the symbols are going to be different.
|
||||
*/
|
||||
if (contains(functionSymbols, checker.getSymbolAtLocation(entry.node), symbolComparer) || isNewExpressionTarget(entry.node)) {
|
||||
const decl = entryToDeclaration(entry);
|
||||
if (decl) {
|
||||
groupedReferences.declarations.push(decl);
|
||||
continue;
|
||||
}
|
||||
|
||||
const call = entryToFunctionCall(entry);
|
||||
if (call) {
|
||||
groupedReferences.functionCalls.push(call);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// if the refactored function is a constructor, we must also check if the references to its class are valid
|
||||
if (isConstructor && contains(classSymbols, checker.getSymbolAtLocation(entry.node), symbolComparer)) {
|
||||
const decl = entryToDeclaration(entry);
|
||||
if (decl) {
|
||||
groupedReferences.declarations.push(decl);
|
||||
continue;
|
||||
}
|
||||
|
||||
const accessExpression = entryToAccessExpression(entry);
|
||||
if (accessExpression) {
|
||||
classReferences.accessExpressions.push(accessExpression);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only class declarations are allowed to be used as a type (in a heritage clause),
|
||||
// otherwise `findAllReferences` might not be able to track constructor calls.
|
||||
if (isClassDeclaration(functionDeclaration.parent)) {
|
||||
const type = entryToType(entry);
|
||||
if (type) {
|
||||
classReferences.typeUsages.push(type);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
groupedReferences.valid = false;
|
||||
}
|
||||
|
||||
return groupedReferences;
|
||||
}
|
||||
}
|
||||
|
||||
function symbolComparer(a: Symbol, b: Symbol): boolean {
|
||||
return getSymbolTarget(a) === getSymbolTarget(b);
|
||||
}
|
||||
|
||||
function entryToDeclaration(entry: FindAllReferences.NodeEntry): Node | undefined {
|
||||
if (isDeclaration(entry.node.parent)) {
|
||||
return entry.node;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function entryToFunctionCall(entry: FindAllReferences.NodeEntry): CallExpression | NewExpression | undefined {
|
||||
if (entry.node.parent) {
|
||||
const functionReference = entry.node;
|
||||
const parent = functionReference.parent;
|
||||
switch (parent.kind) {
|
||||
// Function call (foo(...) or super(...))
|
||||
case SyntaxKind.CallExpression:
|
||||
const callExpression = tryCast(parent, isCallExpression);
|
||||
if (callExpression && callExpression.expression === functionReference) {
|
||||
return callExpression;
|
||||
}
|
||||
break;
|
||||
// Constructor call (new Foo(...))
|
||||
case SyntaxKind.NewExpression:
|
||||
const newExpression = tryCast(parent, isNewExpression);
|
||||
if (newExpression && newExpression.expression === functionReference) {
|
||||
return newExpression;
|
||||
}
|
||||
break;
|
||||
// Method call (x.foo(...))
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
const propertyAccessExpression = tryCast(parent, isPropertyAccessExpression);
|
||||
if (propertyAccessExpression && propertyAccessExpression.parent && propertyAccessExpression.name === functionReference) {
|
||||
const callExpression = tryCast(propertyAccessExpression.parent, isCallExpression);
|
||||
if (callExpression && callExpression.expression === propertyAccessExpression) {
|
||||
return callExpression;
|
||||
}
|
||||
}
|
||||
break;
|
||||
// Method call (x["foo"](...))
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
const elementAccessExpression = tryCast(parent, isElementAccessExpression);
|
||||
if (elementAccessExpression && elementAccessExpression.parent && elementAccessExpression.argumentExpression === functionReference) {
|
||||
const callExpression = tryCast(elementAccessExpression.parent, isCallExpression);
|
||||
if (callExpression && callExpression.expression === elementAccessExpression) {
|
||||
return callExpression;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function entryToAccessExpression(entry: FindAllReferences.NodeEntry): ElementAccessExpression | PropertyAccessExpression | undefined {
|
||||
if (entry.node.parent) {
|
||||
const reference = entry.node;
|
||||
const parent = reference.parent;
|
||||
switch (parent.kind) {
|
||||
// `C.foo`
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
const propertyAccessExpression = tryCast(parent, isPropertyAccessExpression);
|
||||
if (propertyAccessExpression && propertyAccessExpression.expression === reference) {
|
||||
return propertyAccessExpression;
|
||||
}
|
||||
break;
|
||||
// `C["foo"]`
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
const elementAccessExpression = tryCast(parent, isElementAccessExpression);
|
||||
if (elementAccessExpression && elementAccessExpression.expression === reference) {
|
||||
return elementAccessExpression;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function entryToType(entry: FindAllReferences.NodeEntry): Node | undefined {
|
||||
const reference = entry.node;
|
||||
if (getMeaningFromLocation(reference) === SemanticMeaning.Type || isExpressionWithTypeArgumentsInClassExtendsClause(reference.parent)) {
|
||||
return reference;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getFunctionDeclarationAtPosition(file: SourceFile, startPosition: number, checker: TypeChecker): ValidFunctionDeclaration | undefined {
|
||||
const node = getTouchingToken(file, startPosition);
|
||||
const functionDeclaration = getContainingFunction(node);
|
||||
|
||||
// don't offer refactor on top-level JSDoc
|
||||
if (isTopLevelJSDoc(node)) return undefined;
|
||||
|
||||
if (functionDeclaration
|
||||
&& isValidFunctionDeclaration(functionDeclaration, checker)
|
||||
&& rangeContainsRange(functionDeclaration, node)
|
||||
&& !(functionDeclaration.body && rangeContainsRange(functionDeclaration.body, node))) return functionDeclaration;
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isTopLevelJSDoc(node: Node): boolean {
|
||||
const containingJSDoc = findAncestor(node, isJSDocNode);
|
||||
if (containingJSDoc) {
|
||||
const containingNonJSDoc = findAncestor(containingJSDoc, n => !isJSDocNode(n));
|
||||
return !!containingNonJSDoc && isFunctionLikeDeclaration(containingNonJSDoc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isValidFunctionDeclaration(
|
||||
functionDeclaration: SignatureDeclaration,
|
||||
checker: TypeChecker): functionDeclaration is ValidFunctionDeclaration {
|
||||
if (!isValidParameterNodeArray(functionDeclaration.parameters, checker)) return false;
|
||||
switch (functionDeclaration.kind) {
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
return !!functionDeclaration.name
|
||||
&& !!functionDeclaration.body
|
||||
&& !checker.isImplementationOfOverload(functionDeclaration);
|
||||
case SyntaxKind.Constructor:
|
||||
if (isClassDeclaration(functionDeclaration.parent)) {
|
||||
return !!functionDeclaration.body
|
||||
&& !!functionDeclaration.parent.name
|
||||
&& !checker.isImplementationOfOverload(functionDeclaration);
|
||||
}
|
||||
else {
|
||||
return isValidVariableDeclaration(functionDeclaration.parent.parent)
|
||||
&& !!functionDeclaration.body
|
||||
&& !checker.isImplementationOfOverload(functionDeclaration);
|
||||
}
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.ArrowFunction:
|
||||
return isValidVariableDeclaration(functionDeclaration.parent);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isValidParameterNodeArray(
|
||||
parameters: NodeArray<ParameterDeclaration>,
|
||||
checker: TypeChecker): parameters is ValidParameterNodeArray {
|
||||
return getRefactorableParametersLength(parameters) >= minimumParameterLength
|
||||
&& every(parameters, /*callback*/ paramDecl => isValidParameterDeclaration(paramDecl, checker));
|
||||
}
|
||||
|
||||
function isValidParameterDeclaration(
|
||||
parameterDeclaration: ParameterDeclaration,
|
||||
checker: TypeChecker): parameterDeclaration is ValidParameterDeclaration {
|
||||
if (isRestParameter(parameterDeclaration)) {
|
||||
const type = checker.getTypeAtLocation(parameterDeclaration);
|
||||
if (!checker.isArrayType(type) && !checker.isTupleType(type)) return false;
|
||||
}
|
||||
return !parameterDeclaration.modifiers && !parameterDeclaration.decorators && isIdentifier(parameterDeclaration.name);
|
||||
}
|
||||
|
||||
function isValidVariableDeclaration(node: Node): node is ValidVariableDeclaration {
|
||||
return isVariableDeclaration(node) && isVarConst(node) && isIdentifier(node.name) && !node.type; // TODO: GH#30113
|
||||
}
|
||||
|
||||
function hasThisParameter(parameters: NodeArray<ParameterDeclaration>): boolean {
|
||||
return parameters.length > 0 && isThis(parameters[0].name);
|
||||
}
|
||||
|
||||
function getRefactorableParametersLength(parameters: NodeArray<ParameterDeclaration>): number {
|
||||
if (hasThisParameter(parameters)) {
|
||||
return parameters.length - 1;
|
||||
}
|
||||
return parameters.length;
|
||||
}
|
||||
|
||||
function getRefactorableParameters(parameters: NodeArray<ValidParameterDeclaration>): NodeArray<ValidParameterDeclaration> {
|
||||
if (hasThisParameter(parameters)) {
|
||||
parameters = createNodeArray(parameters.slice(1), parameters.hasTrailingComma);
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
function createPropertyOrShorthandAssignment(name: string, initializer: Expression): PropertyAssignment | ShorthandPropertyAssignment {
|
||||
if (isIdentifier(initializer) && getTextOfIdentifierOrLiteral(initializer) === name) {
|
||||
return createShorthandPropertyAssignment(name);
|
||||
}
|
||||
return createPropertyAssignment(name, initializer);
|
||||
}
|
||||
|
||||
function createNewArgument(functionDeclaration: ValidFunctionDeclaration, functionArguments: NodeArray<Expression>): ObjectLiteralExpression {
|
||||
const parameters = getRefactorableParameters(functionDeclaration.parameters);
|
||||
const hasRestParameter = isRestParameter(last(parameters));
|
||||
const nonRestArguments = hasRestParameter ? functionArguments.slice(0, parameters.length - 1) : functionArguments;
|
||||
const properties = map(nonRestArguments, (arg, i) => {
|
||||
const parameterName = getParameterName(parameters[i]);
|
||||
const property = createPropertyOrShorthandAssignment(parameterName, arg);
|
||||
|
||||
suppressLeadingAndTrailingTrivia(property.name);
|
||||
if (isPropertyAssignment(property)) suppressLeadingAndTrailingTrivia(property.initializer);
|
||||
copyComments(arg, property);
|
||||
return property;
|
||||
});
|
||||
|
||||
if (hasRestParameter && functionArguments.length >= parameters.length) {
|
||||
const restArguments = functionArguments.slice(parameters.length - 1);
|
||||
const restProperty = createPropertyAssignment(getParameterName(last(parameters)), createArrayLiteral(restArguments));
|
||||
properties.push(restProperty);
|
||||
}
|
||||
|
||||
const objectLiteral = createObjectLiteral(properties, /*multiLine*/ false);
|
||||
return objectLiteral;
|
||||
}
|
||||
|
||||
function createNewParameters(functionDeclaration: ValidFunctionDeclaration, program: Program, host: LanguageServiceHost): NodeArray<ParameterDeclaration> {
|
||||
const checker = program.getTypeChecker();
|
||||
const refactorableParameters = getRefactorableParameters(functionDeclaration.parameters);
|
||||
const bindingElements = map(refactorableParameters, createBindingElementFromParameterDeclaration);
|
||||
const objectParameterName = createObjectBindingPattern(bindingElements);
|
||||
const objectParameterType = createParameterTypeNode(refactorableParameters);
|
||||
|
||||
let objectInitializer: Expression | undefined;
|
||||
// If every parameter in the original function was optional, add an empty object initializer to the new object parameter
|
||||
if (every(refactorableParameters, isOptionalParameter)) {
|
||||
objectInitializer = createObjectLiteral();
|
||||
}
|
||||
|
||||
const objectParameter = createParameter(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ undefined,
|
||||
/*dotDotDotToken*/ undefined,
|
||||
objectParameterName,
|
||||
/*questionToken*/ undefined,
|
||||
objectParameterType,
|
||||
objectInitializer);
|
||||
|
||||
if (hasThisParameter(functionDeclaration.parameters)) {
|
||||
const thisParameter = functionDeclaration.parameters[0];
|
||||
const newThisParameter = createParameter(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ undefined,
|
||||
/*dotDotDotToken*/ undefined,
|
||||
thisParameter.name,
|
||||
/*questionToken*/ undefined,
|
||||
thisParameter.type);
|
||||
|
||||
suppressLeadingAndTrailingTrivia(newThisParameter.name);
|
||||
copyComments(thisParameter.name, newThisParameter.name);
|
||||
if (thisParameter.type) {
|
||||
suppressLeadingAndTrailingTrivia(newThisParameter.type!);
|
||||
copyComments(thisParameter.type, newThisParameter.type!);
|
||||
}
|
||||
|
||||
return createNodeArray([newThisParameter, objectParameter]);
|
||||
}
|
||||
return createNodeArray([objectParameter]);
|
||||
|
||||
function createBindingElementFromParameterDeclaration(parameterDeclaration: ValidParameterDeclaration): BindingElement {
|
||||
const element = createBindingElement(
|
||||
/*dotDotDotToken*/ undefined,
|
||||
/*propertyName*/ undefined,
|
||||
getParameterName(parameterDeclaration),
|
||||
isRestParameter(parameterDeclaration) && isOptionalParameter(parameterDeclaration) ? createArrayLiteral() : parameterDeclaration.initializer);
|
||||
|
||||
suppressLeadingAndTrailingTrivia(element);
|
||||
if (parameterDeclaration.initializer && element.initializer) {
|
||||
copyComments(parameterDeclaration.initializer, element.initializer);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
function createParameterTypeNode(parameters: NodeArray<ValidParameterDeclaration>): TypeLiteralNode {
|
||||
const members = map(parameters, createPropertySignatureFromParameterDeclaration);
|
||||
const typeNode = addEmitFlags(createTypeLiteralNode(members), EmitFlags.SingleLine);
|
||||
return typeNode;
|
||||
}
|
||||
|
||||
function createPropertySignatureFromParameterDeclaration(parameterDeclaration: ValidParameterDeclaration): PropertySignature {
|
||||
let parameterType = parameterDeclaration.type;
|
||||
if (!parameterType && (parameterDeclaration.initializer || isRestParameter(parameterDeclaration))) {
|
||||
parameterType = getTypeNode(parameterDeclaration);
|
||||
}
|
||||
|
||||
const propertySignature = createPropertySignature(
|
||||
/*modifiers*/ undefined,
|
||||
getParameterName(parameterDeclaration),
|
||||
isOptionalParameter(parameterDeclaration) ? createToken(SyntaxKind.QuestionToken) : parameterDeclaration.questionToken,
|
||||
parameterType,
|
||||
/*initializer*/ undefined);
|
||||
|
||||
suppressLeadingAndTrailingTrivia(propertySignature);
|
||||
copyComments(parameterDeclaration.name, propertySignature.name);
|
||||
if (parameterDeclaration.type && propertySignature.type) {
|
||||
copyComments(parameterDeclaration.type, propertySignature.type);
|
||||
}
|
||||
|
||||
return propertySignature;
|
||||
}
|
||||
|
||||
function getTypeNode(node: Node): TypeNode | undefined {
|
||||
const type = checker.getTypeAtLocation(node);
|
||||
return getTypeNodeIfAccessible(type, node, program, host);
|
||||
}
|
||||
|
||||
function isOptionalParameter(parameterDeclaration: ValidParameterDeclaration): boolean {
|
||||
if (isRestParameter(parameterDeclaration)) {
|
||||
const type = checker.getTypeAtLocation(parameterDeclaration);
|
||||
return !checker.isTupleType(type);
|
||||
}
|
||||
return checker.isOptionalParameter(parameterDeclaration);
|
||||
}
|
||||
}
|
||||
|
||||
function copyComments(sourceNode: Node, targetNode: Node) {
|
||||
const sourceFile = sourceNode.getSourceFile();
|
||||
const text = sourceFile.text;
|
||||
if (hasLeadingLineBreak(sourceNode, text)) {
|
||||
copyLeadingComments(sourceNode, targetNode, sourceFile);
|
||||
}
|
||||
else {
|
||||
copyTrailingAsLeadingComments(sourceNode, targetNode, sourceFile);
|
||||
}
|
||||
copyTrailingComments(sourceNode, targetNode, sourceFile);
|
||||
}
|
||||
|
||||
function hasLeadingLineBreak(node: Node, text: string) {
|
||||
const start = node.getFullStart();
|
||||
const end = node.getStart();
|
||||
for (let i = start; i < end; i++) {
|
||||
if (text.charCodeAt(i) === CharacterCodes.lineFeed) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getParameterName(paramDeclaration: ValidParameterDeclaration) {
|
||||
return getTextOfIdentifierOrLiteral(paramDeclaration.name);
|
||||
}
|
||||
|
||||
function getClassNames(constructorDeclaration: ValidConstructor): Identifier[] {
|
||||
switch (constructorDeclaration.parent.kind) {
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
const classDeclaration = constructorDeclaration.parent;
|
||||
return [classDeclaration.name];
|
||||
case SyntaxKind.ClassExpression:
|
||||
const classExpression = constructorDeclaration.parent;
|
||||
const variableDeclaration = constructorDeclaration.parent.parent;
|
||||
const className = classExpression.name;
|
||||
if (className) return [className, variableDeclaration.name];
|
||||
return [variableDeclaration.name];
|
||||
}
|
||||
}
|
||||
|
||||
function getFunctionNames(functionDeclaration: ValidFunctionDeclaration): Node[] {
|
||||
switch (functionDeclaration.kind) {
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
return [functionDeclaration.name];
|
||||
case SyntaxKind.Constructor:
|
||||
const ctrKeyword = findChildOfKind(functionDeclaration, SyntaxKind.ConstructorKeyword, functionDeclaration.getSourceFile())!;
|
||||
if (functionDeclaration.parent.kind === SyntaxKind.ClassExpression) {
|
||||
const variableDeclaration = functionDeclaration.parent.parent;
|
||||
return [variableDeclaration.name, ctrKeyword];
|
||||
}
|
||||
return [ctrKeyword];
|
||||
case SyntaxKind.ArrowFunction:
|
||||
return [functionDeclaration.parent.name];
|
||||
case SyntaxKind.FunctionExpression:
|
||||
if (functionDeclaration.name) return [functionDeclaration.name, functionDeclaration.parent.name];
|
||||
return [functionDeclaration.parent.name];
|
||||
default:
|
||||
return Debug.assertNever(functionDeclaration);
|
||||
}
|
||||
}
|
||||
|
||||
type ValidParameterNodeArray = NodeArray<ValidParameterDeclaration>;
|
||||
|
||||
interface ValidVariableDeclaration extends VariableDeclaration {
|
||||
name: Identifier;
|
||||
type: undefined;
|
||||
}
|
||||
|
||||
interface ValidConstructor extends ConstructorDeclaration {
|
||||
parent: (ClassDeclaration & { name: Identifier }) | (ClassExpression & { parent: ValidVariableDeclaration });
|
||||
parameters: NodeArray<ValidParameterDeclaration>;
|
||||
body: FunctionBody;
|
||||
}
|
||||
|
||||
interface ValidFunction extends FunctionDeclaration {
|
||||
name: Identifier;
|
||||
parameters: NodeArray<ValidParameterDeclaration>;
|
||||
body: FunctionBody;
|
||||
}
|
||||
|
||||
interface ValidMethod extends MethodDeclaration {
|
||||
parameters: NodeArray<ValidParameterDeclaration>;
|
||||
body: FunctionBody;
|
||||
}
|
||||
|
||||
interface ValidFunctionExpression extends FunctionExpression {
|
||||
parent: ValidVariableDeclaration;
|
||||
parameters: NodeArray<ValidParameterDeclaration>;
|
||||
}
|
||||
|
||||
interface ValidArrowFunction extends ArrowFunction {
|
||||
parent: ValidVariableDeclaration;
|
||||
parameters: NodeArray<ValidParameterDeclaration>;
|
||||
}
|
||||
|
||||
type ValidFunctionDeclaration = ValidConstructor | ValidFunction | ValidMethod | ValidArrowFunction | ValidFunctionExpression;
|
||||
|
||||
interface ValidParameterDeclaration extends ParameterDeclaration {
|
||||
name: Identifier;
|
||||
modifiers: undefined;
|
||||
decorators: undefined;
|
||||
}
|
||||
|
||||
interface GroupedReferences {
|
||||
functionCalls: (CallExpression | NewExpression)[];
|
||||
declarations: Node[];
|
||||
classReferences?: ClassReferences;
|
||||
valid: boolean;
|
||||
}
|
||||
interface ClassReferences {
|
||||
accessExpressions: Node[];
|
||||
typeUsages: Node[];
|
||||
}
|
||||
}
|
||||
@@ -16,18 +16,20 @@ namespace ts {
|
||||
public pos: number;
|
||||
public end: number;
|
||||
public flags: NodeFlags;
|
||||
public modifierFlagsCache: ModifierFlags;
|
||||
public transformFlags: TransformFlags;
|
||||
public parent: Node;
|
||||
public symbol!: Symbol; // Actually optional, but it was too annoying to access `node.symbol!` everywhere since in many cases we know it must be defined
|
||||
public jsDoc?: JSDoc[];
|
||||
public original?: Node;
|
||||
public transformFlags: TransformFlags;
|
||||
private _children: Node[] | undefined;
|
||||
|
||||
constructor(kind: SyntaxKind, pos: number, end: number) {
|
||||
this.pos = pos;
|
||||
this.end = end;
|
||||
this.flags = NodeFlags.None;
|
||||
this.transformFlags = undefined!; // TODO: GH#18217
|
||||
this.modifierFlagsCache = ModifierFlags.None;
|
||||
this.transformFlags = TransformFlags.None;
|
||||
this.parent = undefined!;
|
||||
this.kind = kind;
|
||||
}
|
||||
@@ -200,16 +202,19 @@ namespace ts {
|
||||
public pos: number;
|
||||
public end: number;
|
||||
public flags: NodeFlags;
|
||||
public modifierFlagsCache: ModifierFlags;
|
||||
public transformFlags: TransformFlags;
|
||||
public parent: Node;
|
||||
public symbol!: Symbol;
|
||||
public jsDocComments?: JSDoc[];
|
||||
public transformFlags!: TransformFlags;
|
||||
|
||||
constructor(pos: number, end: number) {
|
||||
// Set properties in same order as NodeObject
|
||||
this.pos = pos;
|
||||
this.end = end;
|
||||
this.flags = NodeFlags.None;
|
||||
this.modifierFlagsCache = ModifierFlags.None;
|
||||
this.transformFlags = TransformFlags.None;
|
||||
this.parent = undefined!;
|
||||
}
|
||||
|
||||
@@ -1153,7 +1158,7 @@ namespace ts {
|
||||
function getValidSourceFile(fileName: string): SourceFile {
|
||||
const sourceFile = program.getSourceFile(fileName);
|
||||
if (!sourceFile) {
|
||||
throw new Error("Could not find file: '" + fileName + "'.");
|
||||
throw new Error(`Could not find sourceFile: '${fileName}' in ${program && JSON.stringify(program.getSourceFiles().map(f => f.fileName))}.`);
|
||||
}
|
||||
return sourceFile;
|
||||
}
|
||||
@@ -1793,7 +1798,7 @@ namespace ts {
|
||||
const span = createTextSpanFromBounds(start, end);
|
||||
const formatContext = formatting.getFormatContext(formatOptions);
|
||||
|
||||
return flatMap(deduplicate(errorCodes, equateValues, compareValues), errorCode => {
|
||||
return flatMap(deduplicate<number>(errorCodes, equateValues, compareValues), errorCode => {
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
return codefix.getFixes({ errorCode, sourceFile, span, program, host, cancellationToken, formatContext, preferences });
|
||||
});
|
||||
|
||||
@@ -452,7 +452,7 @@ namespace ts.SignatureHelp {
|
||||
}
|
||||
|
||||
function getContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile, checker: TypeChecker, isManuallyInvoked: boolean): ArgumentListInfo | undefined {
|
||||
for (let n = node; isManuallyInvoked || (!isBlock(n) && !isSourceFile(n)); n = n.parent) {
|
||||
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)}`);
|
||||
@@ -559,14 +559,14 @@ namespace ts.SignatureHelp {
|
||||
const parameters = (typeParameters || emptyArray).map(t => createSignatureHelpParameterForTypeParameter(t, checker, enclosingDeclaration, sourceFile, printer));
|
||||
const parameterParts = mapToDisplayParts(writer => {
|
||||
const thisParameter = candidateSignature.thisParameter ? [checker.symbolToParameterDeclaration(candidateSignature.thisParameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!] : [];
|
||||
const params = createNodeArray([...thisParameter, ...candidateSignature.parameters.map(param => checker.symbolToParameterDeclaration(param, enclosingDeclaration, signatureHelpNodeBuilderFlags)!)]);
|
||||
const params = createNodeArray([...thisParameter, ...checker.getExpandedParameters(candidateSignature).map(param => checker.symbolToParameterDeclaration(param, enclosingDeclaration, signatureHelpNodeBuilderFlags)!)]);
|
||||
printer.writeList(ListFormat.CallExpressionArguments, params, sourceFile, writer);
|
||||
});
|
||||
return { isVariadic: false, parameters, prefix: [punctuationPart(SyntaxKind.LessThanToken)], suffix: [punctuationPart(SyntaxKind.GreaterThanToken), ...parameterParts] };
|
||||
}
|
||||
|
||||
function itemInfoForParameters(candidateSignature: Signature, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItemInfo {
|
||||
const isVariadic = candidateSignature.hasRestParameter;
|
||||
const isVariadic = checker.hasEffectiveRestParameter(candidateSignature);
|
||||
const printer = createPrinter({ removeComments: true });
|
||||
const typeParameterParts = mapToDisplayParts(writer => {
|
||||
if (candidateSignature.typeParameters && candidateSignature.typeParameters.length) {
|
||||
@@ -574,7 +574,7 @@ namespace ts.SignatureHelp {
|
||||
printer.writeList(ListFormat.TypeParameters, args, sourceFile, writer);
|
||||
}
|
||||
});
|
||||
const parameters = candidateSignature.parameters.map(p => createSignatureHelpParameterForParameter(p, checker, enclosingDeclaration, sourceFile, printer));
|
||||
const parameters = checker.getExpandedParameters(candidateSignature).map(p => createSignatureHelpParameterForParameter(p, checker, enclosingDeclaration, sourceFile, printer));
|
||||
return { isVariadic, parameters, prefix: [...typeParameterParts, punctuationPart(SyntaxKind.OpenParenToken)], suffix: [punctuationPart(SyntaxKind.CloseParenToken)] };
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ namespace ts.Completions.StringCompletions {
|
||||
case Extension.Jsx: return ScriptElementKindModifier.jsxModifier;
|
||||
case Extension.Ts: return ScriptElementKindModifier.tsModifier;
|
||||
case Extension.Tsx: return ScriptElementKindModifier.tsxModifier;
|
||||
case Extension.TsBuildInfo: return Debug.fail(`Extension ${Extension.TsBuildInfo} is unsupported.`);
|
||||
case undefined: return ScriptElementKindModifier.none;
|
||||
default:
|
||||
return Debug.assertNever(extension);
|
||||
|
||||
@@ -310,7 +310,7 @@ namespace ts.SymbolDisplay {
|
||||
displayParts.push(spacePart());
|
||||
addFullSymbolName(symbol);
|
||||
}
|
||||
if (symbolFlags & SymbolFlags.Module) {
|
||||
if (symbolFlags & SymbolFlags.Module && !isThisExpression) {
|
||||
prefixNextMeaning();
|
||||
const declaration = getDeclarationOfKind<ModuleDeclaration>(symbol, SyntaxKind.ModuleDeclaration);
|
||||
const isNamespace = declaration && declaration.name && declaration.name.kind === SyntaxKind.Identifier;
|
||||
|
||||
+42
-29
@@ -28,17 +28,27 @@ namespace ts.textChanges {
|
||||
}
|
||||
|
||||
export interface ConfigurableStart {
|
||||
/** True to use getStart() (NB, not getFullStart()) without adjustment. */
|
||||
useNonAdjustedStartPosition?: boolean;
|
||||
leadingTriviaOption?: LeadingTriviaOption;
|
||||
}
|
||||
export interface ConfigurableEnd {
|
||||
/** True to use getEnd() without adjustment. */
|
||||
useNonAdjustedEndPosition?: boolean;
|
||||
trailingTriviaOption?: TrailingTriviaOption;
|
||||
}
|
||||
|
||||
export enum Position {
|
||||
FullStart,
|
||||
Start
|
||||
export enum LeadingTriviaOption {
|
||||
/** Exclude all leading trivia (use getStart()) */
|
||||
Exclude,
|
||||
/** Include leading trivia and,
|
||||
* if there are no line breaks between the node and the previous token,
|
||||
* include all trivia between the node and the previous token
|
||||
*/
|
||||
IncludeAll,
|
||||
}
|
||||
|
||||
export enum TrailingTriviaOption {
|
||||
/** Exclude all trailing trivia (use getEnd()) */
|
||||
Exclude,
|
||||
/** Include trailing trivia */
|
||||
Include,
|
||||
}
|
||||
|
||||
function skipWhitespacesAndLineBreaks(text: string, start: number) {
|
||||
@@ -68,13 +78,14 @@ namespace ts.textChanges {
|
||||
* Usually leading trivia of the variable declaration 'y' should not include trailing trivia (whitespace, comment 'this is x' and newline) from the preceding
|
||||
* variable declaration and trailing trivia for 'y' should include (whitespace, comment 'this is y', newline).
|
||||
* By default when removing nodes we adjust start and end positions to respect specification of the trivia above.
|
||||
* If pos\end should be interpreted literally 'useNonAdjustedStartPosition' or 'useNonAdjustedEndPosition' should be set to true
|
||||
* If pos\end should be interpreted literally (that is, withouth including leading and trailing trivia), `leadingTriviaOption` should be set to `LeadingTriviaOption.Exclude`
|
||||
* and `trailingTriviaOption` to `TrailingTriviaOption.Exclude`.
|
||||
*/
|
||||
export interface ConfigurableStartEnd extends ConfigurableStart, ConfigurableEnd {}
|
||||
|
||||
export const useNonAdjustedPositions: ConfigurableStartEnd = {
|
||||
useNonAdjustedStartPosition: true,
|
||||
useNonAdjustedEndPosition: true,
|
||||
const useNonAdjustedPositions: ConfigurableStartEnd = {
|
||||
leadingTriviaOption: LeadingTriviaOption.Exclude,
|
||||
trailingTriviaOption: TrailingTriviaOption.Exclude,
|
||||
};
|
||||
|
||||
export interface InsertNodeOptions {
|
||||
@@ -143,11 +154,12 @@ namespace ts.textChanges {
|
||||
}
|
||||
|
||||
function getAdjustedRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd): TextRange {
|
||||
return { pos: getAdjustedStartPosition(sourceFile, startNode, options, Position.Start), end: getAdjustedEndPosition(sourceFile, endNode, options) };
|
||||
return { pos: getAdjustedStartPosition(sourceFile, startNode, options), end: getAdjustedEndPosition(sourceFile, endNode, options) };
|
||||
}
|
||||
|
||||
function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStart, position: Position) {
|
||||
if (options.useNonAdjustedStartPosition) {
|
||||
function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStart) {
|
||||
const { leadingTriviaOption } = options;
|
||||
if (leadingTriviaOption === LeadingTriviaOption.Exclude) {
|
||||
return node.getStart(sourceFile);
|
||||
}
|
||||
const fullStart = node.getFullStart();
|
||||
@@ -165,7 +177,7 @@ namespace ts.textChanges {
|
||||
// fullstart
|
||||
// when b is replaced - we usually want to keep the leading trvia
|
||||
// when b is deleted - we delete it
|
||||
return position === Position.Start ? start : fullStart;
|
||||
return leadingTriviaOption === LeadingTriviaOption.IncludeAll ? fullStart : start;
|
||||
}
|
||||
// get start position of the line following the line that contains fullstart position
|
||||
// (but only if the fullstart isn't the very beginning of the file)
|
||||
@@ -178,11 +190,12 @@ namespace ts.textChanges {
|
||||
|
||||
function getAdjustedEndPosition(sourceFile: SourceFile, node: Node, options: ConfigurableEnd) {
|
||||
const { end } = node;
|
||||
if (options.useNonAdjustedEndPosition || isExpression(node)) {
|
||||
const { trailingTriviaOption } = options;
|
||||
if (trailingTriviaOption === TrailingTriviaOption.Exclude || (isExpression(node) && trailingTriviaOption !== TrailingTriviaOption.Include)) {
|
||||
return end;
|
||||
}
|
||||
const newEnd = skipTrivia(sourceFile.text, end, /*stopAfterLineBreak*/ true);
|
||||
return newEnd !== end && isLineBreak(sourceFile.text.charCodeAt(newEnd - 1))
|
||||
return newEnd !== end && (trailingTriviaOption === TrailingTriviaOption.Include || isLineBreak(sourceFile.text.charCodeAt(newEnd - 1)))
|
||||
? newEnd
|
||||
: end;
|
||||
}
|
||||
@@ -240,15 +253,15 @@ namespace ts.textChanges {
|
||||
this.deleteRange(sourceFile, { pos: modifier.getStart(sourceFile), end: skipTrivia(sourceFile.text, modifier.end, /*stopAfterLineBreak*/ true) });
|
||||
}
|
||||
|
||||
public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = {}): void {
|
||||
const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.FullStart);
|
||||
public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void {
|
||||
const startPosition = getAdjustedStartPosition(sourceFile, startNode, options);
|
||||
const endPosition = getAdjustedEndPosition(sourceFile, endNode, options);
|
||||
this.deleteRange(sourceFile, { pos: startPosition, end: endPosition });
|
||||
}
|
||||
|
||||
public deleteNodeRangeExcludingEnd(sourceFile: SourceFile, startNode: Node, afterEndNode: Node | undefined, options: ConfigurableStartEnd = {}): void {
|
||||
const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.FullStart);
|
||||
const endPosition = afterEndNode === undefined ? sourceFile.text.length : getAdjustedStartPosition(sourceFile, afterEndNode, options, Position.FullStart);
|
||||
public deleteNodeRangeExcludingEnd(sourceFile: SourceFile, startNode: Node, afterEndNode: Node | undefined, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void {
|
||||
const startPosition = getAdjustedStartPosition(sourceFile, startNode, options);
|
||||
const endPosition = afterEndNode === undefined ? sourceFile.text.length : getAdjustedStartPosition(sourceFile, afterEndNode, options);
|
||||
this.deleteRange(sourceFile, { pos: startPosition, end: endPosition });
|
||||
}
|
||||
|
||||
@@ -307,7 +320,7 @@ namespace ts.textChanges {
|
||||
}
|
||||
|
||||
public insertNodeBefore(sourceFile: SourceFile, before: Node, newNode: Node, blankLineBetween = false): void {
|
||||
this.insertNodeAt(sourceFile, getAdjustedStartPosition(sourceFile, before, {}, Position.Start), newNode, this.getOptionsForInsertNodeBefore(before, blankLineBetween));
|
||||
this.insertNodeAt(sourceFile, getAdjustedStartPosition(sourceFile, before, {}), newNode, this.getOptionsForInsertNodeBefore(before, blankLineBetween));
|
||||
}
|
||||
|
||||
public insertModifierBefore(sourceFile: SourceFile, modifier: SyntaxKind, before: Node): void {
|
||||
@@ -427,7 +440,7 @@ namespace ts.textChanges {
|
||||
}
|
||||
|
||||
public insertNodeAtEndOfScope(sourceFile: SourceFile, scope: Node, newNode: Node): void {
|
||||
const pos = getAdjustedStartPosition(sourceFile, scope.getLastToken()!, {}, Position.Start);
|
||||
const pos = getAdjustedStartPosition(sourceFile, scope.getLastToken()!, {});
|
||||
this.insertNodeAt(sourceFile, pos, newNode, {
|
||||
prefix: isLineBreak(sourceFile.text.charCodeAt(scope.getLastToken()!.pos)) ? this.newLineCharacter : this.newLineCharacter + this.newLineCharacter,
|
||||
suffix: this.newLineCharacter
|
||||
@@ -736,7 +749,7 @@ namespace ts.textChanges {
|
||||
|
||||
// find first non-whitespace position in the leading trivia of the node
|
||||
function startPositionToDeleteNodeInList(sourceFile: SourceFile, node: Node): number {
|
||||
return skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, {}, Position.FullStart), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true);
|
||||
return skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, { leadingTriviaOption: LeadingTriviaOption.IncludeAll }), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true);
|
||||
}
|
||||
|
||||
function getClassOrObjectBraceEnds(cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression, sourceFile: SourceFile): [number, number] {
|
||||
@@ -1090,7 +1103,7 @@ namespace ts.textChanges {
|
||||
case SyntaxKind.ImportDeclaration:
|
||||
deleteNode(changes, sourceFile, node,
|
||||
// For first import, leave header comment in place
|
||||
node === sourceFile.imports[0].parent ? { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: false } : undefined);
|
||||
node === sourceFile.imports[0].parent ? { leadingTriviaOption: LeadingTriviaOption.Exclude } : undefined);
|
||||
break;
|
||||
|
||||
case SyntaxKind.BindingElement:
|
||||
@@ -1134,7 +1147,7 @@ namespace ts.textChanges {
|
||||
deleteNodeInList(changes, deletedNodesInLists, sourceFile, node);
|
||||
}
|
||||
else {
|
||||
deleteNode(changes, sourceFile, node, node.kind === SyntaxKind.SemicolonToken ? { useNonAdjustedEndPosition: true } : undefined);
|
||||
deleteNode(changes, sourceFile, node, node.kind === SyntaxKind.SemicolonToken ? { trailingTriviaOption: TrailingTriviaOption.Exclude } : undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1213,8 +1226,8 @@ namespace ts.textChanges {
|
||||
|
||||
/** Warning: This deletes comments too. See `copyComments` in `convertFunctionToEs6Class`. */
|
||||
// Exported for tests only! (TODO: improve tests to not need this)
|
||||
export function deleteNode(changes: ChangeTracker, sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = {}): void {
|
||||
const startPosition = getAdjustedStartPosition(sourceFile, node, options, Position.FullStart);
|
||||
export function deleteNode(changes: ChangeTracker, sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void {
|
||||
const startPosition = getAdjustedStartPosition(sourceFile, node, options);
|
||||
const endPosition = getAdjustedEndPosition(sourceFile, node, options);
|
||||
changes.deleteRange(sourceFile, { pos: startPosition, end: endPosition });
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
"refactors/generateGetAccessorAndSetAccessor.ts",
|
||||
"refactors/moveToNewFile.ts",
|
||||
"refactors/addOrRemoveBracesToArrowFunction.ts",
|
||||
"refactors/convertParamsToDestructuredObject.ts",
|
||||
"services.ts",
|
||||
"breakpoints.ts",
|
||||
"transform.ts",
|
||||
|
||||
@@ -905,7 +905,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
function isWhiteSpaceOnlyJsxText(node: Node): boolean {
|
||||
return isJsxText(node) && node.containsOnlyWhiteSpaces;
|
||||
return isJsxText(node) && node.containsOnlyTriviaWhiteSpaces;
|
||||
}
|
||||
|
||||
export function isInTemplateString(sourceFile: SourceFile, position: number) {
|
||||
@@ -1315,7 +1315,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
export function getQuotePreference(sourceFile: SourceFile, preferences: UserPreferences): QuotePreference {
|
||||
if (preferences.quotePreference) {
|
||||
if (preferences.quotePreference && preferences.quotePreference !== "auto") {
|
||||
return preferences.quotePreference === "single" ? QuotePreference.Single : QuotePreference.Double;
|
||||
}
|
||||
else {
|
||||
@@ -1664,6 +1664,18 @@ namespace ts {
|
||||
return ensureScriptKind(fileName, host && host.getScriptKind && host.getScriptKind(fileName));
|
||||
}
|
||||
|
||||
export function getSymbolTarget(symbol: Symbol): Symbol {
|
||||
let next: Symbol = symbol;
|
||||
while (isTransientSymbol(next) && next.target) {
|
||||
next = next.target;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
function isTransientSymbol(symbol: Symbol): symbol is TransientSymbol {
|
||||
return (symbol.flags & SymbolFlags.Transient) !== 0;
|
||||
}
|
||||
|
||||
export function getUniqueSymbolId(symbol: Symbol, checker: TypeChecker) {
|
||||
return getSymbolId(skipAlias(symbol, checker));
|
||||
}
|
||||
@@ -1821,8 +1833,28 @@ namespace ts {
|
||||
return lastPos;
|
||||
}
|
||||
|
||||
export function copyComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) {
|
||||
forEachLeadingCommentRange(sourceFile.text, sourceNode.pos, (pos, end, kind, htnl) => {
|
||||
export function copyLeadingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) {
|
||||
forEachLeadingCommentRange(sourceFile.text, sourceNode.pos, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticLeadingComment));
|
||||
}
|
||||
|
||||
|
||||
export function copyTrailingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) {
|
||||
forEachTrailingCommentRange(sourceFile.text, sourceNode.end, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticTrailingComment));
|
||||
}
|
||||
|
||||
/**
|
||||
* This function copies the trailing comments for the token that comes before `sourceNode`, as leading comments of `targetNode`.
|
||||
* This is useful because sometimes a comment that refers to `sourceNode` will be a leading comment for `sourceNode`, according to the
|
||||
* notion of trivia ownership, and instead will be a trailing comment for the token before `sourceNode`, e.g.:
|
||||
* `function foo(\* not leading comment for a *\ a: string) {}`
|
||||
* The comment refers to `a` but belongs to the `(` token, but we might want to copy it.
|
||||
*/
|
||||
export function copyTrailingAsLeadingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) {
|
||||
forEachTrailingCommentRange(sourceFile.text, sourceNode.pos, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticLeadingComment));
|
||||
}
|
||||
|
||||
function getAddCommentsFunction(targetNode: Node, sourceFile: SourceFile, commentKind: CommentKind | undefined, hasTrailingNewLine: boolean | undefined, cb: (node: Node, kind: CommentKind, text: string, hasTrailingNewLine?: boolean) => void) {
|
||||
return (pos: number, end: number, kind: CommentKind, htnl: boolean) => {
|
||||
if (kind === SyntaxKind.MultiLineCommentTrivia) {
|
||||
// Remove leading /*
|
||||
pos += 2;
|
||||
@@ -1833,8 +1865,8 @@ namespace ts {
|
||||
// Remove leading //
|
||||
pos += 2;
|
||||
}
|
||||
addSyntheticLeadingComment(targetNode, commentKind || kind, sourceFile.text.slice(pos, end), hasTrailingNewLine !== undefined ? hasTrailingNewLine : htnl);
|
||||
});
|
||||
cb(targetNode, commentKind || kind, sourceFile.text.slice(pos, end), hasTrailingNewLine !== undefined ? hasTrailingNewLine : htnl);
|
||||
};
|
||||
}
|
||||
|
||||
function indexInTextChange(change: string, name: string): number {
|
||||
@@ -1868,15 +1900,18 @@ namespace ts {
|
||||
if (/^\d+$/.test(text)) {
|
||||
return text;
|
||||
}
|
||||
// Editors can pass in undefined or empty string - we want to infer the preference in those cases.
|
||||
const quotePreference = preferences.quotePreference || "auto";
|
||||
const quoted = JSON.stringify(text);
|
||||
switch (preferences.quotePreference) {
|
||||
case undefined:
|
||||
switch (quotePreference) {
|
||||
// TODO use getQuotePreference to infer the actual quote style.
|
||||
case "auto":
|
||||
case "double":
|
||||
return quoted;
|
||||
case "single":
|
||||
return `'${stripQuotes(quoted).replace("'", "\\'").replace('\\"', '"')}'`;
|
||||
default:
|
||||
return Debug.assertNever(preferences.quotePreference);
|
||||
return Debug.assertNever(quotePreference);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1911,4 +1946,28 @@ namespace ts {
|
||||
export function getSwitchedType(caseClause: CaseClause, checker: TypeChecker): Type | undefined {
|
||||
return checker.getTypeAtLocation(caseClause.parent.parent.expression);
|
||||
}
|
||||
|
||||
export function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, program: Program, host: LanguageServiceHost): TypeNode | undefined {
|
||||
const checker = program.getTypeChecker();
|
||||
let typeIsAccessible = true;
|
||||
const notAccessible = () => { typeIsAccessible = false; };
|
||||
const res = checker.typeToTypeNode(type, enclosingScope, /*flags*/ undefined, {
|
||||
trackSymbol: (symbol, declaration, meaning) => {
|
||||
// TODO: GH#18217
|
||||
typeIsAccessible = typeIsAccessible && checker.isSymbolAccessible(symbol, declaration, meaning!, /*shouldComputeAliasToMarkVisible*/ false).accessibility === SymbolAccessibility.Accessible;
|
||||
},
|
||||
reportInaccessibleThisError: notAccessible,
|
||||
reportPrivateInBaseOfClassExpression: notAccessible,
|
||||
reportInaccessibleUniqueSymbolError: notAccessible,
|
||||
moduleResolverHost: {
|
||||
readFile: host.readFile,
|
||||
fileExists: host.fileExists,
|
||||
directoryExists: host.directoryExists,
|
||||
getSourceFiles: program.getSourceFiles,
|
||||
getCurrentDirectory: program.getCurrentDirectory,
|
||||
getCommonSourceDirectory: program.getCommonSourceDirectory,
|
||||
}
|
||||
});
|
||||
return typeIsAccessible ? res : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user