mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 08:11:30 -06:00
Move codefix types into services/types.ts (#35506)
* Move codefix types into services/types.ts * Also split apart refactorProvider * Move all meanings of DocumentHighlights into one file * Use setter for object allocator * Remove unneeded namespace reference * Remove some shorthand references to nonlocal namespace variables * Convert WatchType string enum to a structure that can be merged across modules * Rename harness to harnessIO * Move accidental globals into namespace * Remove unused globals * Suppress lints - these qualifiers are needed for the migration to go smoothly * Hide global declaration
This commit is contained in:
parent
9a766c3197
commit
27616dd523
@ -4958,6 +4958,10 @@ namespace ts {
|
||||
getSourceMapSourceConstructor: () => <any>SourceMapSource,
|
||||
};
|
||||
|
||||
export function setObjectAllocator(alloc: ObjectAllocator) {
|
||||
objectAllocator = alloc;
|
||||
}
|
||||
|
||||
export function formatStringFromArgs(text: string, args: ArrayLike<string | number>, baseIndex = 0): string {
|
||||
return text.replace(/{(\d+)}/g, (_match, index: string) => "" + Debug.assertDefined(args[+index + baseIndex]));
|
||||
}
|
||||
|
||||
@ -243,13 +243,23 @@ namespace ts {
|
||||
};
|
||||
}
|
||||
|
||||
export const enum WatchType {
|
||||
ConfigFile = "Config file",
|
||||
SourceFile = "Source file",
|
||||
MissingFile = "Missing file",
|
||||
WildcardDirectory = "Wild card directory",
|
||||
FailedLookupLocations = "Failed Lookup Locations",
|
||||
TypeRoots = "Type roots"
|
||||
export type WatchType = WatchTypeRegistry[keyof WatchTypeRegistry];
|
||||
export const WatchType: WatchTypeRegistry = {
|
||||
ConfigFile: "Config file",
|
||||
SourceFile: "Source file",
|
||||
MissingFile: "Missing file",
|
||||
WildcardDirectory: "Wild card directory",
|
||||
FailedLookupLocations: "Failed Lookup Locations",
|
||||
TypeRoots: "Type roots"
|
||||
};
|
||||
|
||||
export interface WatchTypeRegistry {
|
||||
ConfigFile: "Config file",
|
||||
SourceFile: "Source file",
|
||||
MissingFile: "Missing file",
|
||||
WildcardDirectory: "Wild card directory",
|
||||
FailedLookupLocations: "Failed Lookup Locations",
|
||||
TypeRoots: "Type roots"
|
||||
}
|
||||
|
||||
interface WatchFactory<X, Y = undefined> extends ts.WatchFactory<X, Y> {
|
||||
|
||||
@ -27,22 +27,4 @@ var assert: typeof _chai.assert = _chai.assert;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var global: NodeJS.Global = Function("return this").call(undefined); // eslint-disable-line no-new-func
|
||||
|
||||
declare var window: {};
|
||||
declare var XMLHttpRequest: new() => XMLHttpRequest;
|
||||
|
||||
interface XMLHttpRequest {
|
||||
readonly readyState: number;
|
||||
readonly responseText: string;
|
||||
readonly status: number;
|
||||
readonly statusText: string;
|
||||
open(method: string, url: string, async?: boolean, user?: string, password?: string): void;
|
||||
send(data?: string): void;
|
||||
setRequestHeader(header: string, value: string): void;
|
||||
getAllResponseHeaders(): string;
|
||||
getResponseHeader(header: string): string | null;
|
||||
overrideMimeType(mime: string): void;
|
||||
}
|
||||
/* eslint-enable no-var */
|
||||
@ -33,7 +33,7 @@
|
||||
"sourceMapRecorder.ts",
|
||||
"harnessGlobals.ts",
|
||||
"harnessUtils.ts",
|
||||
"harness.ts",
|
||||
"harnessIO.ts",
|
||||
"harnessLanguageService.ts",
|
||||
"virtualFileSystemWithWatch.ts",
|
||||
"fourslash.ts",
|
||||
|
||||
@ -1,175 +1,177 @@
|
||||
interface TypeWriterTypeResult {
|
||||
line: number;
|
||||
syntaxKind: number;
|
||||
sourceText: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface TypeWriterSymbolResult {
|
||||
line: number;
|
||||
syntaxKind: number;
|
||||
sourceText: string;
|
||||
symbol: string;
|
||||
}
|
||||
|
||||
interface TypeWriterResult {
|
||||
line: number;
|
||||
syntaxKind: number;
|
||||
sourceText: string;
|
||||
symbol?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
class TypeWriterWalker {
|
||||
currentSourceFile!: ts.SourceFile;
|
||||
|
||||
private checker: ts.TypeChecker;
|
||||
|
||||
constructor(private program: ts.Program, fullTypeCheck: boolean, private hadErrorBaseline: boolean) {
|
||||
// Consider getting both the diagnostics checker and the non-diagnostics checker to verify
|
||||
// they are consistent.
|
||||
this.checker = fullTypeCheck
|
||||
? program.getDiagnosticsProducingTypeChecker()
|
||||
: program.getTypeChecker();
|
||||
namespace Harness {
|
||||
export interface TypeWriterTypeResult {
|
||||
line: number;
|
||||
syntaxKind: number;
|
||||
sourceText: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
public *getSymbols(fileName: string): IterableIterator<TypeWriterSymbolResult> {
|
||||
const sourceFile = this.program.getSourceFile(fileName)!;
|
||||
this.currentSourceFile = sourceFile;
|
||||
const gen = this.visitNode(sourceFile, /*isSymbolWalk*/ true);
|
||||
for (let {done, value} = gen.next(); !done; { done, value } = gen.next()) {
|
||||
yield value as TypeWriterSymbolResult;
|
||||
}
|
||||
export interface TypeWriterSymbolResult {
|
||||
line: number;
|
||||
syntaxKind: number;
|
||||
sourceText: string;
|
||||
symbol: string;
|
||||
}
|
||||
|
||||
public *getTypes(fileName: string): IterableIterator<TypeWriterTypeResult> {
|
||||
const sourceFile = this.program.getSourceFile(fileName)!;
|
||||
this.currentSourceFile = sourceFile;
|
||||
const gen = this.visitNode(sourceFile, /*isSymbolWalk*/ false);
|
||||
for (let {done, value} = gen.next(); !done; { done, value } = gen.next()) {
|
||||
yield value as TypeWriterTypeResult;
|
||||
}
|
||||
export interface TypeWriterResult {
|
||||
line: number;
|
||||
syntaxKind: number;
|
||||
sourceText: string;
|
||||
symbol?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
private *visitNode(node: ts.Node, isSymbolWalk: boolean): IterableIterator<TypeWriterResult> {
|
||||
if (ts.isExpressionNode(node) || node.kind === ts.SyntaxKind.Identifier || ts.isDeclarationName(node)) {
|
||||
const result = this.writeTypeOrSymbol(node, isSymbolWalk);
|
||||
if (result) {
|
||||
yield result;
|
||||
}
|
||||
export class TypeWriterWalker {
|
||||
currentSourceFile!: ts.SourceFile;
|
||||
|
||||
private checker: ts.TypeChecker;
|
||||
|
||||
constructor(private program: ts.Program, fullTypeCheck: boolean, private hadErrorBaseline: boolean) {
|
||||
// Consider getting both the diagnostics checker and the non-diagnostics checker to verify
|
||||
// they are consistent.
|
||||
this.checker = fullTypeCheck
|
||||
? program.getDiagnosticsProducingTypeChecker()
|
||||
: program.getTypeChecker();
|
||||
}
|
||||
|
||||
const children: ts.Node[] = [];
|
||||
ts.forEachChild(node, child => void children.push(child));
|
||||
for (const child of children) {
|
||||
const gen = this.visitNode(child, isSymbolWalk);
|
||||
public *getSymbols(fileName: string): IterableIterator<TypeWriterSymbolResult> {
|
||||
const sourceFile = this.program.getSourceFile(fileName)!;
|
||||
this.currentSourceFile = sourceFile;
|
||||
const gen = this.visitNode(sourceFile, /*isSymbolWalk*/ true);
|
||||
for (let {done, value} = gen.next(); !done; { done, value } = gen.next()) {
|
||||
yield value;
|
||||
yield value as TypeWriterSymbolResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isImportStatementName(node: ts.Node) {
|
||||
if (ts.isImportSpecifier(node.parent) && (node.parent.name === node || node.parent.propertyName === node)) return true;
|
||||
if (ts.isImportClause(node.parent) && node.parent.name === node) return true;
|
||||
if (ts.isImportEqualsDeclaration(node.parent) && node.parent.name === node) return true;
|
||||
return false;
|
||||
}
|
||||
public *getTypes(fileName: string): IterableIterator<TypeWriterTypeResult> {
|
||||
const sourceFile = this.program.getSourceFile(fileName)!;
|
||||
this.currentSourceFile = sourceFile;
|
||||
const gen = this.visitNode(sourceFile, /*isSymbolWalk*/ false);
|
||||
for (let {done, value} = gen.next(); !done; { done, value } = gen.next()) {
|
||||
yield value as TypeWriterTypeResult;
|
||||
}
|
||||
}
|
||||
|
||||
private isExportStatementName(node: ts.Node) {
|
||||
if (ts.isExportAssignment(node.parent) && node.parent.expression === node) return true;
|
||||
if (ts.isExportSpecifier(node.parent) && (node.parent.name === node || node.parent.propertyName === node)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private isIntrinsicJsxTag(node: ts.Node) {
|
||||
const p = node.parent;
|
||||
if (!(ts.isJsxOpeningElement(p) || ts.isJsxClosingElement(p) || ts.isJsxSelfClosingElement(p))) return false;
|
||||
if (p.tagName !== node) return false;
|
||||
return ts.isIntrinsicJsxName(node.getText());
|
||||
}
|
||||
|
||||
private writeTypeOrSymbol(node: ts.Node, isSymbolWalk: boolean): TypeWriterResult | undefined {
|
||||
const actualPos = ts.skipTrivia(this.currentSourceFile.text, node.pos);
|
||||
const lineAndCharacter = this.currentSourceFile.getLineAndCharacterOfPosition(actualPos);
|
||||
const sourceText = ts.getSourceTextOfNodeFromSourceFile(this.currentSourceFile, node);
|
||||
|
||||
if (!isSymbolWalk) {
|
||||
// Don't try to get the type of something that's already a type.
|
||||
// Exception for `T` in `type T = something` because that may evaluate to some interesting type.
|
||||
if (ts.isPartOfTypeNode(node) || ts.isIdentifier(node) && !(ts.getMeaningFromDeclaration(node.parent) & ts.SemanticMeaning.Value) && !(ts.isTypeAliasDeclaration(node.parent) && node.parent.name === node)) {
|
||||
return undefined;
|
||||
private *visitNode(node: ts.Node, isSymbolWalk: boolean): IterableIterator<TypeWriterResult> {
|
||||
if (ts.isExpressionNode(node) || node.kind === ts.SyntaxKind.Identifier || ts.isDeclarationName(node)) {
|
||||
const result = this.writeTypeOrSymbol(node, isSymbolWalk);
|
||||
if (result) {
|
||||
yield result;
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround to ensure we output 'C' instead of 'typeof C' for base class expressions
|
||||
// let type = this.checker.getTypeAtLocation(node);
|
||||
let type = ts.isExpressionWithTypeArgumentsInClassExtendsClause(node.parent) ? this.checker.getTypeAtLocation(node.parent) : undefined;
|
||||
if (!type || type.flags & ts.TypeFlags.Any) type = this.checker.getTypeAtLocation(node);
|
||||
const typeString =
|
||||
// Distinguish `errorType`s from `any`s; but only if the file has no errors.
|
||||
// Additionally,
|
||||
// * the LHS of a qualified name
|
||||
// * a binding pattern name
|
||||
// * labels
|
||||
// * the "global" in "declare global"
|
||||
// * the "target" in "new.target"
|
||||
// * names in import statements
|
||||
// * type-only names in export statements
|
||||
// * and intrinsic jsx tag names
|
||||
// return `error`s via `getTypeAtLocation`
|
||||
// But this is generally expected, so we don't call those out, either
|
||||
(!this.hadErrorBaseline &&
|
||||
type.flags & ts.TypeFlags.Any &&
|
||||
!ts.isBindingElement(node.parent) &&
|
||||
!ts.isPropertyAccessOrQualifiedName(node.parent) &&
|
||||
!ts.isLabelName(node) &&
|
||||
!(ts.isModuleDeclaration(node.parent) && ts.isGlobalScopeAugmentation(node.parent)) &&
|
||||
!ts.isMetaProperty(node.parent) &&
|
||||
!this.isImportStatementName(node) &&
|
||||
!this.isExportStatementName(node) &&
|
||||
!this.isIntrinsicJsxTag(node)) ?
|
||||
(type as ts.IntrinsicType).intrinsicName :
|
||||
this.checker.typeToString(type, node.parent, ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.AllowUniqueESSymbolType);
|
||||
const children: ts.Node[] = [];
|
||||
ts.forEachChild(node, child => void children.push(child));
|
||||
for (const child of children) {
|
||||
const gen = this.visitNode(child, isSymbolWalk);
|
||||
for (let {done, value} = gen.next(); !done; { done, value } = gen.next()) {
|
||||
yield value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isImportStatementName(node: ts.Node) {
|
||||
if (ts.isImportSpecifier(node.parent) && (node.parent.name === node || node.parent.propertyName === node)) return true;
|
||||
if (ts.isImportClause(node.parent) && node.parent.name === node) return true;
|
||||
if (ts.isImportEqualsDeclaration(node.parent) && node.parent.name === node) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private isExportStatementName(node: ts.Node) {
|
||||
if (ts.isExportAssignment(node.parent) && node.parent.expression === node) return true;
|
||||
if (ts.isExportSpecifier(node.parent) && (node.parent.name === node || node.parent.propertyName === node)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private isIntrinsicJsxTag(node: ts.Node) {
|
||||
const p = node.parent;
|
||||
if (!(ts.isJsxOpeningElement(p) || ts.isJsxClosingElement(p) || ts.isJsxSelfClosingElement(p))) return false;
|
||||
if (p.tagName !== node) return false;
|
||||
return ts.isIntrinsicJsxName(node.getText());
|
||||
}
|
||||
|
||||
private writeTypeOrSymbol(node: ts.Node, isSymbolWalk: boolean): TypeWriterResult | undefined {
|
||||
const actualPos = ts.skipTrivia(this.currentSourceFile.text, node.pos);
|
||||
const lineAndCharacter = this.currentSourceFile.getLineAndCharacterOfPosition(actualPos);
|
||||
const sourceText = ts.getSourceTextOfNodeFromSourceFile(this.currentSourceFile, node);
|
||||
|
||||
if (!isSymbolWalk) {
|
||||
// Don't try to get the type of something that's already a type.
|
||||
// Exception for `T` in `type T = something` because that may evaluate to some interesting type.
|
||||
if (ts.isPartOfTypeNode(node) || ts.isIdentifier(node) && !(ts.getMeaningFromDeclaration(node.parent) & ts.SemanticMeaning.Value) && !(ts.isTypeAliasDeclaration(node.parent) && node.parent.name === node)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Workaround to ensure we output 'C' instead of 'typeof C' for base class expressions
|
||||
// let type = this.checker.getTypeAtLocation(node);
|
||||
let type = ts.isExpressionWithTypeArgumentsInClassExtendsClause(node.parent) ? this.checker.getTypeAtLocation(node.parent) : undefined;
|
||||
if (!type || type.flags & ts.TypeFlags.Any) type = this.checker.getTypeAtLocation(node);
|
||||
const typeString =
|
||||
// Distinguish `errorType`s from `any`s; but only if the file has no errors.
|
||||
// Additionally,
|
||||
// * the LHS of a qualified name
|
||||
// * a binding pattern name
|
||||
// * labels
|
||||
// * the "global" in "declare global"
|
||||
// * the "target" in "new.target"
|
||||
// * names in import statements
|
||||
// * type-only names in export statements
|
||||
// * and intrinsic jsx tag names
|
||||
// return `error`s via `getTypeAtLocation`
|
||||
// But this is generally expected, so we don't call those out, either
|
||||
(!this.hadErrorBaseline &&
|
||||
type.flags & ts.TypeFlags.Any &&
|
||||
!ts.isBindingElement(node.parent) &&
|
||||
!ts.isPropertyAccessOrQualifiedName(node.parent) &&
|
||||
!ts.isLabelName(node) &&
|
||||
!(ts.isModuleDeclaration(node.parent) && ts.isGlobalScopeAugmentation(node.parent)) &&
|
||||
!ts.isMetaProperty(node.parent) &&
|
||||
!this.isImportStatementName(node) &&
|
||||
!this.isExportStatementName(node) &&
|
||||
!this.isIntrinsicJsxTag(node)) ?
|
||||
(type as ts.IntrinsicType).intrinsicName :
|
||||
this.checker.typeToString(type, node.parent, ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.AllowUniqueESSymbolType);
|
||||
return {
|
||||
line: lineAndCharacter.line,
|
||||
syntaxKind: node.kind,
|
||||
sourceText,
|
||||
type: typeString
|
||||
};
|
||||
}
|
||||
const symbol = this.checker.getSymbolAtLocation(node);
|
||||
if (!symbol) {
|
||||
return;
|
||||
}
|
||||
let symbolString = "Symbol(" + this.checker.symbolToString(symbol, node.parent);
|
||||
if (symbol.declarations) {
|
||||
let count = 0;
|
||||
for (const declaration of symbol.declarations) {
|
||||
if (count >= 5) {
|
||||
symbolString += ` ... and ${symbol.declarations.length - count} more`;
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
symbolString += ", ";
|
||||
if ((declaration as any).__symbolTestOutputCache) {
|
||||
symbolString += (declaration as any).__symbolTestOutputCache;
|
||||
continue;
|
||||
}
|
||||
const declSourceFile = declaration.getSourceFile();
|
||||
const declLineAndCharacter = declSourceFile.getLineAndCharacterOfPosition(declaration.pos);
|
||||
const fileName = ts.getBaseFileName(declSourceFile.fileName);
|
||||
const isLibFile = /lib(.*)\.d\.ts/i.test(fileName);
|
||||
const declText = `Decl(${ fileName }, ${ isLibFile ? "--" : declLineAndCharacter.line }, ${ isLibFile ? "--" : declLineAndCharacter.character })`;
|
||||
symbolString += declText;
|
||||
(declaration as any).__symbolTestOutputCache = declText;
|
||||
}
|
||||
}
|
||||
symbolString += ")";
|
||||
return {
|
||||
line: lineAndCharacter.line,
|
||||
syntaxKind: node.kind,
|
||||
sourceText,
|
||||
type: typeString
|
||||
symbol: symbolString
|
||||
};
|
||||
}
|
||||
const symbol = this.checker.getSymbolAtLocation(node);
|
||||
if (!symbol) {
|
||||
return;
|
||||
}
|
||||
let symbolString = "Symbol(" + this.checker.symbolToString(symbol, node.parent);
|
||||
if (symbol.declarations) {
|
||||
let count = 0;
|
||||
for (const declaration of symbol.declarations) {
|
||||
if (count >= 5) {
|
||||
symbolString += ` ... and ${symbol.declarations.length - count} more`;
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
symbolString += ", ";
|
||||
if ((declaration as any).__symbolTestOutputCache) {
|
||||
symbolString += (declaration as any).__symbolTestOutputCache;
|
||||
continue;
|
||||
}
|
||||
const declSourceFile = declaration.getSourceFile();
|
||||
const declLineAndCharacter = declSourceFile.getLineAndCharacterOfPosition(declaration.pos);
|
||||
const fileName = ts.getBaseFileName(declSourceFile.fileName);
|
||||
const isLibFile = /lib(.*)\.d\.ts/i.test(fileName);
|
||||
const declText = `Decl(${ fileName }, ${ isLibFile ? "--" : declLineAndCharacter.line }, ${ isLibFile ? "--" : declLineAndCharacter.character })`;
|
||||
symbolString += declText;
|
||||
(declaration as any).__symbolTestOutputCache = declText;
|
||||
}
|
||||
}
|
||||
symbolString += ")";
|
||||
return {
|
||||
line: lineAndCharacter.line,
|
||||
syntaxKind: node.kind,
|
||||
sourceText,
|
||||
symbol: symbolString
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1625,7 +1625,7 @@ namespace ts.server {
|
||||
totalNonTsFileSize += this.host.getFileSize(fileName);
|
||||
|
||||
if (totalNonTsFileSize > maxProgramSizeForNonTsFiles || totalNonTsFileSize > availableSpace) {
|
||||
this.logger.info(getExceedLimitMessage({ propertyReader, hasTSFileExtension, host: this.host }, totalNonTsFileSize));
|
||||
this.logger.info(getExceedLimitMessage({ propertyReader, hasTSFileExtension: ts.hasTSFileExtension, host: this.host }, totalNonTsFileSize)); // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier
|
||||
// Keep the size as zero since it's disabled
|
||||
return fileName;
|
||||
}
|
||||
@ -1694,7 +1694,7 @@ namespace ts.server {
|
||||
configFileName: configFileName(),
|
||||
projectType: project instanceof ExternalProject ? "external" : "configured",
|
||||
languageServiceEnabled: project.languageServiceEnabled,
|
||||
version,
|
||||
version: ts.version, // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier
|
||||
};
|
||||
this.eventHandler({ eventName: ProjectInfoTelemetryEvent, data });
|
||||
|
||||
|
||||
@ -1575,7 +1575,7 @@ namespace ts.server.protocol {
|
||||
/**
|
||||
* List of last known projects
|
||||
*/
|
||||
knownProjects: protocol.ProjectVersionInfo[];
|
||||
knownProjects: ProjectVersionInfo[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -2147,7 +2147,7 @@ namespace ts.server {
|
||||
|
||||
private handlers = createMapFromTemplate<(request: protocol.Request) => HandlerResponse>({
|
||||
[CommandNames.Status]: () => {
|
||||
const response: protocol.StatusResponseBody = { version };
|
||||
const response: protocol.StatusResponseBody = { version: ts.version }; // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier
|
||||
return this.requiredResponse(response);
|
||||
},
|
||||
[CommandNames.OpenExternalProject]: (request: protocol.OpenExternalProjectRequest) => {
|
||||
|
||||
@ -1,13 +1,20 @@
|
||||
/* @internal */
|
||||
namespace ts {
|
||||
// Additional tsserver specific watch information
|
||||
export const enum WatchType {
|
||||
ClosedScriptInfo = "Closed Script info",
|
||||
ConfigFileForInferredRoot = "Config file for the inferred project root",
|
||||
NodeModulesForClosedScriptInfo = "node_modules for closed script infos in them",
|
||||
MissingSourceMapFile = "Missing source map file",
|
||||
NoopConfigFileForInferredRoot = "Noop Config file for the inferred project root",
|
||||
MissingGeneratedFile = "Missing generated file",
|
||||
PackageJsonFile = "package.json file for import suggestions"
|
||||
export interface WatchTypeRegistry {
|
||||
ClosedScriptInfo: "Closed Script info",
|
||||
ConfigFileForInferredRoot: "Config file for the inferred project root",
|
||||
NodeModulesForClosedScriptInfo: "node_modules for closed script infos in them",
|
||||
MissingSourceMapFile: "Missing source map file",
|
||||
NoopConfigFileForInferredRoot: "Noop Config file for the inferred project root",
|
||||
MissingGeneratedFile: "Missing generated file",
|
||||
PackageJsonFile: "package.json file for import suggestions"
|
||||
}
|
||||
WatchType.ClosedScriptInfo = "Closed Script info";
|
||||
WatchType.ConfigFileForInferredRoot = "Config file for the inferred project root";
|
||||
WatchType.NodeModulesForClosedScriptInfo = "node_modules for closed script infos in them";
|
||||
WatchType.MissingSourceMapFile = "Missing source map file";
|
||||
WatchType.NoopConfigFileForInferredRoot = "Noop Config file for the inferred project root";
|
||||
WatchType.MissingGeneratedFile = "Missing generated file";
|
||||
WatchType.PackageJsonFile = "package.json file for import suggestions";
|
||||
}
|
||||
@ -1,99 +1,74 @@
|
||||
/* @internal */
|
||||
namespace ts {
|
||||
export interface CodeFixRegistration {
|
||||
errorCodes: readonly number[];
|
||||
getCodeActions(context: CodeFixContext): CodeFixAction[] | undefined;
|
||||
fixIds?: readonly string[];
|
||||
getAllCodeActions?(context: CodeFixAllContext): CombinedCodeActions;
|
||||
namespace ts.codefix {
|
||||
const errorCodeToFixes = createMultiMap<CodeFixRegistration>();
|
||||
const fixIdToRegistration = createMap<CodeFixRegistration>();
|
||||
|
||||
export type DiagnosticAndArguments = DiagnosticMessage | [DiagnosticMessage, string] | [DiagnosticMessage, string, string];
|
||||
function diagnosticToString(diag: DiagnosticAndArguments): string {
|
||||
return isArray(diag)
|
||||
? formatStringFromArgs(getLocaleSpecificMessage(diag[0]), diag.slice(1) as readonly string[])
|
||||
: getLocaleSpecificMessage(diag);
|
||||
}
|
||||
|
||||
export interface CodeFixContextBase extends textChanges.TextChangesContext {
|
||||
sourceFile: SourceFile;
|
||||
program: Program;
|
||||
cancellationToken: CancellationToken;
|
||||
preferences: UserPreferences;
|
||||
export function createCodeFixActionNoFixId(fixName: string, changes: FileTextChanges[], description: DiagnosticAndArguments) {
|
||||
return createCodeFixActionWorker(fixName, diagnosticToString(description), changes, /*fixId*/ undefined, /*fixAllDescription*/ undefined);
|
||||
}
|
||||
|
||||
export interface CodeFixAllContext extends CodeFixContextBase {
|
||||
fixId: {};
|
||||
export function createCodeFixAction(fixName: string, changes: FileTextChanges[], description: DiagnosticAndArguments, fixId: {}, fixAllDescription: DiagnosticAndArguments, command?: CodeActionCommand): CodeFixAction {
|
||||
return createCodeFixActionWorker(fixName, diagnosticToString(description), changes, fixId, diagnosticToString(fixAllDescription), command);
|
||||
}
|
||||
|
||||
export interface CodeFixContext extends CodeFixContextBase {
|
||||
errorCode: number;
|
||||
span: TextSpan;
|
||||
function createCodeFixActionWorker(fixName: string, description: string, changes: FileTextChanges[], fixId?: {}, fixAllDescription?: string, command?: CodeActionCommand): CodeFixAction {
|
||||
return { fixName, description, changes, fixId, fixAllDescription, commands: command ? [command] : undefined };
|
||||
}
|
||||
|
||||
export namespace codefix {
|
||||
const errorCodeToFixes = createMultiMap<CodeFixRegistration>();
|
||||
const fixIdToRegistration = createMap<CodeFixRegistration>();
|
||||
|
||||
export type DiagnosticAndArguments = DiagnosticMessage | [DiagnosticMessage, string] | [DiagnosticMessage, string, string];
|
||||
function diagnosticToString(diag: DiagnosticAndArguments): string {
|
||||
return isArray(diag)
|
||||
? formatStringFromArgs(getLocaleSpecificMessage(diag[0]), diag.slice(1) as readonly string[])
|
||||
: getLocaleSpecificMessage(diag);
|
||||
export function registerCodeFix(reg: CodeFixRegistration) {
|
||||
for (const error of reg.errorCodes) {
|
||||
errorCodeToFixes.add(String(error), reg);
|
||||
}
|
||||
|
||||
export function createCodeFixActionNoFixId(fixName: string, changes: FileTextChanges[], description: DiagnosticAndArguments) {
|
||||
return createCodeFixActionWorker(fixName, diagnosticToString(description), changes, /*fixId*/ undefined, /*fixAllDescription*/ undefined);
|
||||
}
|
||||
|
||||
export function createCodeFixAction(fixName: string, changes: FileTextChanges[], description: DiagnosticAndArguments, fixId: {}, fixAllDescription: DiagnosticAndArguments, command?: CodeActionCommand): CodeFixAction {
|
||||
return createCodeFixActionWorker(fixName, diagnosticToString(description), changes, fixId, diagnosticToString(fixAllDescription), command);
|
||||
}
|
||||
|
||||
function createCodeFixActionWorker(fixName: string, description: string, changes: FileTextChanges[], fixId?: {}, fixAllDescription?: string, command?: CodeActionCommand): CodeFixAction {
|
||||
return { fixName, description, changes, fixId, fixAllDescription, commands: command ? [command] : undefined };
|
||||
}
|
||||
|
||||
export function registerCodeFix(reg: CodeFixRegistration) {
|
||||
for (const error of reg.errorCodes) {
|
||||
errorCodeToFixes.add(String(error), reg);
|
||||
}
|
||||
if (reg.fixIds) {
|
||||
for (const fixId of reg.fixIds) {
|
||||
Debug.assert(!fixIdToRegistration.has(fixId));
|
||||
fixIdToRegistration.set(fixId, reg);
|
||||
}
|
||||
if (reg.fixIds) {
|
||||
for (const fixId of reg.fixIds) {
|
||||
Debug.assert(!fixIdToRegistration.has(fixId));
|
||||
fixIdToRegistration.set(fixId, reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getSupportedErrorCodes(): string[] {
|
||||
return arrayFrom(errorCodeToFixes.keys());
|
||||
}
|
||||
export function getSupportedErrorCodes(): string[] {
|
||||
return arrayFrom(errorCodeToFixes.keys());
|
||||
}
|
||||
|
||||
export function getFixes(context: CodeFixContext): readonly CodeFixAction[] {
|
||||
return flatMap(errorCodeToFixes.get(String(context.errorCode)) || emptyArray, f => f.getCodeActions(context));
|
||||
}
|
||||
export function getFixes(context: CodeFixContext): readonly CodeFixAction[] {
|
||||
return flatMap(errorCodeToFixes.get(String(context.errorCode)) || emptyArray, f => f.getCodeActions(context));
|
||||
}
|
||||
|
||||
export function getAllFixes(context: CodeFixAllContext): CombinedCodeActions {
|
||||
// Currently fixId is always a string.
|
||||
return fixIdToRegistration.get(cast(context.fixId, isString))!.getAllCodeActions!(context);
|
||||
}
|
||||
export function getAllFixes(context: CodeFixAllContext): CombinedCodeActions {
|
||||
// Currently fixId is always a string.
|
||||
return fixIdToRegistration.get(cast(context.fixId, isString))!.getAllCodeActions!(context);
|
||||
}
|
||||
|
||||
export function createCombinedCodeActions(changes: FileTextChanges[], commands?: CodeActionCommand[]): CombinedCodeActions {
|
||||
return { changes, commands };
|
||||
}
|
||||
export function createCombinedCodeActions(changes: FileTextChanges[], commands?: CodeActionCommand[]): CombinedCodeActions {
|
||||
return { changes, commands };
|
||||
}
|
||||
|
||||
export function createFileTextChanges(fileName: string, textChanges: TextChange[]): FileTextChanges {
|
||||
return { fileName, textChanges };
|
||||
}
|
||||
export function createFileTextChanges(fileName: string, textChanges: TextChange[]): FileTextChanges {
|
||||
return { fileName, textChanges };
|
||||
}
|
||||
|
||||
export function codeFixAll(
|
||||
context: CodeFixAllContext,
|
||||
errorCodes: number[],
|
||||
use: (changes: textChanges.ChangeTracker, error: DiagnosticWithLocation, commands: Push<CodeActionCommand>) => void,
|
||||
): CombinedCodeActions {
|
||||
const commands: CodeActionCommand[] = [];
|
||||
const changes = textChanges.ChangeTracker.with(context, t => eachDiagnostic(context, errorCodes, diag => use(t, diag, commands)));
|
||||
return createCombinedCodeActions(changes, commands.length === 0 ? undefined : commands);
|
||||
}
|
||||
export function codeFixAll(
|
||||
context: CodeFixAllContext,
|
||||
errorCodes: number[],
|
||||
use: (changes: textChanges.ChangeTracker, error: DiagnosticWithLocation, commands: Push<CodeActionCommand>) => void,
|
||||
): CombinedCodeActions {
|
||||
const commands: CodeActionCommand[] = [];
|
||||
const changes = textChanges.ChangeTracker.with(context, t => eachDiagnostic(context, errorCodes, diag => use(t, diag, commands)));
|
||||
return createCombinedCodeActions(changes, commands.length === 0 ? undefined : commands);
|
||||
}
|
||||
|
||||
export function eachDiagnostic({ program, sourceFile, cancellationToken }: CodeFixAllContext, errorCodes: readonly number[], cb: (diag: DiagnosticWithLocation) => void): void {
|
||||
for (const diag of program.getSemanticDiagnostics(sourceFile, cancellationToken).concat(computeSuggestionDiagnostics(sourceFile, program, cancellationToken))) {
|
||||
if (contains(errorCodes, diag.code)) {
|
||||
cb(diag as DiagnosticWithLocation);
|
||||
}
|
||||
export function eachDiagnostic({ program, sourceFile, cancellationToken }: CodeFixAllContext, errorCodes: readonly number[], cb: (diag: DiagnosticWithLocation) => void): void {
|
||||
for (const diag of program.getSemanticDiagnostics(sourceFile, cancellationToken).concat(computeSuggestionDiagnostics(sourceFile, program, cancellationToken))) {
|
||||
if (contains(errorCodes, diag.code)) {
|
||||
cb(diag as DiagnosticWithLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,500 +1,507 @@
|
||||
/* @internal */
|
||||
namespace ts.DocumentHighlights {
|
||||
export function getDocumentHighlights(program: Program, cancellationToken: CancellationToken, sourceFile: SourceFile, position: number, sourceFilesToSearch: readonly SourceFile[]): DocumentHighlights[] | undefined {
|
||||
const node = getTouchingPropertyName(sourceFile, position);
|
||||
|
||||
if (node.parent && (isJsxOpeningElement(node.parent) && node.parent.tagName === node || isJsxClosingElement(node.parent))) {
|
||||
// For a JSX element, just highlight the matching tag, not all references.
|
||||
const { openingElement, closingElement } = node.parent.parent;
|
||||
const highlightSpans = [openingElement, closingElement].map(({ tagName }) => getHighlightSpanForNode(tagName, sourceFile));
|
||||
return [{ fileName: sourceFile.fileName, highlightSpans }];
|
||||
}
|
||||
|
||||
return getSemanticDocumentHighlights(position, node, program, cancellationToken, sourceFilesToSearch) || getSyntacticDocumentHighlights(node, sourceFile);
|
||||
namespace ts {
|
||||
export interface DocumentHighlights {
|
||||
fileName: string;
|
||||
highlightSpans: HighlightSpan[];
|
||||
}
|
||||
|
||||
function getHighlightSpanForNode(node: Node, sourceFile: SourceFile): HighlightSpan {
|
||||
return {
|
||||
fileName: sourceFile.fileName,
|
||||
textSpan: createTextSpanFromNode(node, sourceFile),
|
||||
kind: HighlightSpanKind.none
|
||||
};
|
||||
}
|
||||
/* @internal */
|
||||
export namespace DocumentHighlights {
|
||||
export function getDocumentHighlights(program: Program, cancellationToken: CancellationToken, sourceFile: SourceFile, position: number, sourceFilesToSearch: readonly SourceFile[]): DocumentHighlights[] | undefined {
|
||||
const node = getTouchingPropertyName(sourceFile, position);
|
||||
|
||||
function getSemanticDocumentHighlights(position: number, node: Node, program: Program, cancellationToken: CancellationToken, sourceFilesToSearch: readonly SourceFile[]): DocumentHighlights[] | undefined {
|
||||
const sourceFilesSet = arrayToSet(sourceFilesToSearch, f => f.fileName);
|
||||
const referenceEntries = FindAllReferences.getReferenceEntriesForNode(position, node, program, sourceFilesToSearch, cancellationToken, /*options*/ undefined, sourceFilesSet);
|
||||
if (!referenceEntries) return undefined;
|
||||
const map = arrayToMultiMap(referenceEntries.map(FindAllReferences.toHighlightSpan), e => e.fileName, e => e.span);
|
||||
return arrayFrom(map.entries(), ([fileName, highlightSpans]) => {
|
||||
if (!sourceFilesSet.has(fileName)) {
|
||||
Debug.assert(program.redirectTargetsMap.has(fileName));
|
||||
const redirectTarget = program.getSourceFile(fileName);
|
||||
const redirect = find(sourceFilesToSearch, f => !!f.redirectInfo && f.redirectInfo.redirectTarget === redirectTarget)!;
|
||||
fileName = redirect.fileName;
|
||||
Debug.assert(sourceFilesSet.has(fileName));
|
||||
}
|
||||
return { fileName, highlightSpans };
|
||||
});
|
||||
}
|
||||
|
||||
function getSyntacticDocumentHighlights(node: Node, sourceFile: SourceFile): DocumentHighlights[] | undefined {
|
||||
const highlightSpans = getHighlightSpans(node, sourceFile);
|
||||
return highlightSpans && [{ fileName: sourceFile.fileName, highlightSpans }];
|
||||
}
|
||||
|
||||
function getHighlightSpans(node: Node, sourceFile: SourceFile): HighlightSpan[] | undefined {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.IfKeyword:
|
||||
case SyntaxKind.ElseKeyword:
|
||||
return isIfStatement(node.parent) ? getIfElseOccurrences(node.parent, sourceFile) : undefined;
|
||||
case SyntaxKind.ReturnKeyword:
|
||||
return useParent(node.parent, isReturnStatement, getReturnOccurrences);
|
||||
case SyntaxKind.ThrowKeyword:
|
||||
return useParent(node.parent, isThrowStatement, getThrowOccurrences);
|
||||
case SyntaxKind.TryKeyword:
|
||||
case SyntaxKind.CatchKeyword:
|
||||
case SyntaxKind.FinallyKeyword:
|
||||
const tryStatement = node.kind === SyntaxKind.CatchKeyword ? node.parent.parent : node.parent;
|
||||
return useParent(tryStatement, isTryStatement, getTryCatchFinallyOccurrences);
|
||||
case SyntaxKind.SwitchKeyword:
|
||||
return useParent(node.parent, isSwitchStatement, getSwitchCaseDefaultOccurrences);
|
||||
case SyntaxKind.CaseKeyword:
|
||||
case SyntaxKind.DefaultKeyword:
|
||||
return useParent(node.parent.parent.parent, isSwitchStatement, getSwitchCaseDefaultOccurrences);
|
||||
case SyntaxKind.BreakKeyword:
|
||||
case SyntaxKind.ContinueKeyword:
|
||||
return useParent(node.parent, isBreakOrContinueStatement, getBreakOrContinueStatementOccurrences);
|
||||
case SyntaxKind.ForKeyword:
|
||||
case SyntaxKind.WhileKeyword:
|
||||
case SyntaxKind.DoKeyword:
|
||||
return useParent(node.parent, (n): n is IterationStatement => isIterationStatement(n, /*lookInLabeledStatements*/ true), getLoopBreakContinueOccurrences);
|
||||
case SyntaxKind.ConstructorKeyword:
|
||||
return getFromAllDeclarations(isConstructorDeclaration, [SyntaxKind.ConstructorKeyword]);
|
||||
case SyntaxKind.GetKeyword:
|
||||
case SyntaxKind.SetKeyword:
|
||||
return getFromAllDeclarations(isAccessor, [SyntaxKind.GetKeyword, SyntaxKind.SetKeyword]);
|
||||
case SyntaxKind.AwaitKeyword:
|
||||
return useParent(node.parent, isAwaitExpression, getAsyncAndAwaitOccurrences);
|
||||
case SyntaxKind.AsyncKeyword:
|
||||
return highlightSpans(getAsyncAndAwaitOccurrences(node));
|
||||
case SyntaxKind.YieldKeyword:
|
||||
return highlightSpans(getYieldOccurrences(node));
|
||||
default:
|
||||
return isModifierKind(node.kind) && (isDeclaration(node.parent) || isVariableStatement(node.parent))
|
||||
? highlightSpans(getModifierOccurrences(node.kind, node.parent))
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function getFromAllDeclarations<T extends Node>(nodeTest: (node: Node) => node is T, keywords: readonly SyntaxKind[]): HighlightSpan[] | undefined {
|
||||
return useParent(node.parent, nodeTest, decl => mapDefined(decl.symbol.declarations, d =>
|
||||
nodeTest(d) ? find(d.getChildren(sourceFile), c => contains(keywords, c.kind)) : undefined));
|
||||
}
|
||||
|
||||
function useParent<T extends Node>(node: Node, nodeTest: (node: Node) => node is T, getNodes: (node: T, sourceFile: SourceFile) => readonly Node[] | undefined): HighlightSpan[] | undefined {
|
||||
return nodeTest(node) ? highlightSpans(getNodes(node, sourceFile)) : undefined;
|
||||
}
|
||||
|
||||
function highlightSpans(nodes: readonly Node[] | undefined): HighlightSpan[] | undefined {
|
||||
return nodes && nodes.map(node => getHighlightSpanForNode(node, sourceFile));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregates all throw-statements within this node *without* crossing
|
||||
* into function boundaries and try-blocks with catch-clauses.
|
||||
*/
|
||||
function aggregateOwnedThrowStatements(node: Node): readonly ThrowStatement[] | undefined {
|
||||
if (isThrowStatement(node)) {
|
||||
return [node];
|
||||
}
|
||||
else if (isTryStatement(node)) {
|
||||
// Exceptions thrown within a try block lacking a catch clause are "owned" in the current context.
|
||||
return concatenate(
|
||||
node.catchClause ? aggregateOwnedThrowStatements(node.catchClause) : node.tryBlock && aggregateOwnedThrowStatements(node.tryBlock),
|
||||
node.finallyBlock && aggregateOwnedThrowStatements(node.finallyBlock));
|
||||
}
|
||||
// Do not cross function boundaries.
|
||||
return isFunctionLike(node) ? undefined : flatMapChildren(node, aggregateOwnedThrowStatements);
|
||||
}
|
||||
|
||||
/**
|
||||
* For lack of a better name, this function takes a throw statement and returns the
|
||||
* nearest ancestor that is a try-block (whose try statement has a catch clause),
|
||||
* function-block, or source file.
|
||||
*/
|
||||
function getThrowStatementOwner(throwStatement: ThrowStatement): Node | undefined {
|
||||
let child: Node = throwStatement;
|
||||
|
||||
while (child.parent) {
|
||||
const parent = child.parent;
|
||||
|
||||
if (isFunctionBlock(parent) || parent.kind === SyntaxKind.SourceFile) {
|
||||
return parent;
|
||||
if (node.parent && (isJsxOpeningElement(node.parent) && node.parent.tagName === node || isJsxClosingElement(node.parent))) {
|
||||
// For a JSX element, just highlight the matching tag, not all references.
|
||||
const { openingElement, closingElement } = node.parent.parent;
|
||||
const highlightSpans = [openingElement, closingElement].map(({ tagName }) => getHighlightSpanForNode(tagName, sourceFile));
|
||||
return [{ fileName: sourceFile.fileName, highlightSpans }];
|
||||
}
|
||||
|
||||
// A throw-statement is only owned by a try-statement if the try-statement has
|
||||
// a catch clause, and if the throw-statement occurs within the try block.
|
||||
if (isTryStatement(parent) && parent.tryBlock === child && parent.catchClause) {
|
||||
return child;
|
||||
}
|
||||
|
||||
child = parent;
|
||||
return getSemanticDocumentHighlights(position, node, program, cancellationToken, sourceFilesToSearch) || getSyntacticDocumentHighlights(node, sourceFile);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
function getHighlightSpanForNode(node: Node, sourceFile: SourceFile): HighlightSpan {
|
||||
return {
|
||||
fileName: sourceFile.fileName,
|
||||
textSpan: createTextSpanFromNode(node, sourceFile),
|
||||
kind: HighlightSpanKind.none
|
||||
};
|
||||
}
|
||||
|
||||
function aggregateAllBreakAndContinueStatements(node: Node): readonly BreakOrContinueStatement[] | undefined {
|
||||
return isBreakOrContinueStatement(node) ? [node] : isFunctionLike(node) ? undefined : flatMapChildren(node, aggregateAllBreakAndContinueStatements);
|
||||
}
|
||||
|
||||
function flatMapChildren<T>(node: Node, cb: (child: Node) => readonly T[] | T | undefined): readonly T[] {
|
||||
const result: T[] = [];
|
||||
node.forEachChild(child => {
|
||||
const value = cb(child);
|
||||
if (value !== undefined) {
|
||||
result.push(...toArray(value));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function ownsBreakOrContinueStatement(owner: Node, statement: BreakOrContinueStatement): boolean {
|
||||
const actualOwner = getBreakOrContinueOwner(statement);
|
||||
return !!actualOwner && actualOwner === owner;
|
||||
}
|
||||
|
||||
function getBreakOrContinueOwner(statement: BreakOrContinueStatement): Node | undefined {
|
||||
return findAncestor(statement, node => {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.SwitchStatement:
|
||||
if (statement.kind === SyntaxKind.ContinueStatement) {
|
||||
return false;
|
||||
}
|
||||
// falls through
|
||||
|
||||
case SyntaxKind.ForStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.ForOfStatement:
|
||||
case SyntaxKind.WhileStatement:
|
||||
case SyntaxKind.DoStatement:
|
||||
return !statement.label || isLabeledBy(node, statement.label.escapedText);
|
||||
default:
|
||||
// Don't cross function boundaries.
|
||||
// TODO: GH#20090
|
||||
return isFunctionLike(node) && "quit";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getModifierOccurrences(modifier: Modifier["kind"], declaration: Node): Node[] {
|
||||
return mapDefined(getNodesToSearchForModifier(declaration, modifierToFlag(modifier)), node => findModifier(node, modifier));
|
||||
}
|
||||
|
||||
function getNodesToSearchForModifier(declaration: Node, modifierFlag: ModifierFlags): readonly Node[] | undefined {
|
||||
// Types of node whose children might have modifiers.
|
||||
const container = declaration.parent as ModuleBlock | SourceFile | Block | CaseClause | DefaultClause | ConstructorDeclaration | MethodDeclaration | FunctionDeclaration | ObjectTypeDeclaration;
|
||||
switch (container.kind) {
|
||||
case SyntaxKind.ModuleBlock:
|
||||
case SyntaxKind.SourceFile:
|
||||
case SyntaxKind.Block:
|
||||
case SyntaxKind.CaseClause:
|
||||
case SyntaxKind.DefaultClause:
|
||||
// Container is either a class declaration or the declaration is a classDeclaration
|
||||
if (modifierFlag & ModifierFlags.Abstract && isClassDeclaration(declaration)) {
|
||||
return [...declaration.members, declaration];
|
||||
}
|
||||
else {
|
||||
return container.statements;
|
||||
}
|
||||
case SyntaxKind.Constructor:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
return [...container.parameters, ...(isClassLike(container.parent) ? container.parent.members : [])];
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.ClassExpression:
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
case SyntaxKind.TypeLiteral:
|
||||
const nodes = container.members;
|
||||
|
||||
// If we're an accessibility modifier, we're in an instance member and should search
|
||||
// the constructor's parameter list for instance members as well.
|
||||
if (modifierFlag & (ModifierFlags.AccessibilityModifier | ModifierFlags.Readonly)) {
|
||||
const constructor = find(container.members, isConstructorDeclaration);
|
||||
if (constructor) {
|
||||
return [...nodes, ...constructor.parameters];
|
||||
}
|
||||
}
|
||||
else if (modifierFlag & ModifierFlags.Abstract) {
|
||||
return [...nodes, container];
|
||||
}
|
||||
return nodes;
|
||||
default:
|
||||
Debug.assertNever(container, "Invalid container kind.");
|
||||
}
|
||||
}
|
||||
|
||||
function pushKeywordIf(keywordList: Push<Node>, token: Node | undefined, ...expected: SyntaxKind[]): boolean {
|
||||
if (token && contains(expected, token.kind)) {
|
||||
keywordList.push(token);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getLoopBreakContinueOccurrences(loopNode: IterationStatement): Node[] {
|
||||
const keywords: Node[] = [];
|
||||
|
||||
if (pushKeywordIf(keywords, loopNode.getFirstToken(), SyntaxKind.ForKeyword, SyntaxKind.WhileKeyword, SyntaxKind.DoKeyword)) {
|
||||
// If we succeeded and got a do-while loop, then start looking for a 'while' keyword.
|
||||
if (loopNode.kind === SyntaxKind.DoStatement) {
|
||||
const loopTokens = loopNode.getChildren();
|
||||
|
||||
for (let i = loopTokens.length - 1; i >= 0; i--) {
|
||||
if (pushKeywordIf(keywords, loopTokens[i], SyntaxKind.WhileKeyword)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
forEach(aggregateAllBreakAndContinueStatements(loopNode.statement), statement => {
|
||||
if (ownsBreakOrContinueStatement(loopNode, statement)) {
|
||||
pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword);
|
||||
}
|
||||
});
|
||||
|
||||
return keywords;
|
||||
}
|
||||
|
||||
function getBreakOrContinueStatementOccurrences(breakOrContinueStatement: BreakOrContinueStatement): Node[] | undefined {
|
||||
const owner = getBreakOrContinueOwner(breakOrContinueStatement);
|
||||
|
||||
if (owner) {
|
||||
switch (owner.kind) {
|
||||
case SyntaxKind.ForStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.ForOfStatement:
|
||||
case SyntaxKind.DoStatement:
|
||||
case SyntaxKind.WhileStatement:
|
||||
return getLoopBreakContinueOccurrences(<IterationStatement>owner);
|
||||
case SyntaxKind.SwitchStatement:
|
||||
return getSwitchCaseDefaultOccurrences(<SwitchStatement>owner);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getSwitchCaseDefaultOccurrences(switchStatement: SwitchStatement): Node[] {
|
||||
const keywords: Node[] = [];
|
||||
|
||||
pushKeywordIf(keywords, switchStatement.getFirstToken(), SyntaxKind.SwitchKeyword);
|
||||
|
||||
// Go through each clause in the switch statement, collecting the 'case'/'default' keywords.
|
||||
forEach(switchStatement.caseBlock.clauses, clause => {
|
||||
pushKeywordIf(keywords, clause.getFirstToken(), SyntaxKind.CaseKeyword, SyntaxKind.DefaultKeyword);
|
||||
|
||||
forEach(aggregateAllBreakAndContinueStatements(clause), statement => {
|
||||
if (ownsBreakOrContinueStatement(switchStatement, statement)) {
|
||||
pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword);
|
||||
function getSemanticDocumentHighlights(position: number, node: Node, program: Program, cancellationToken: CancellationToken, sourceFilesToSearch: readonly SourceFile[]): DocumentHighlights[] | undefined {
|
||||
const sourceFilesSet = arrayToSet(sourceFilesToSearch, f => f.fileName);
|
||||
const referenceEntries = FindAllReferences.getReferenceEntriesForNode(position, node, program, sourceFilesToSearch, cancellationToken, /*options*/ undefined, sourceFilesSet);
|
||||
if (!referenceEntries) return undefined;
|
||||
const map = arrayToMultiMap(referenceEntries.map(FindAllReferences.toHighlightSpan), e => e.fileName, e => e.span);
|
||||
return arrayFrom(map.entries(), ([fileName, highlightSpans]) => {
|
||||
if (!sourceFilesSet.has(fileName)) {
|
||||
Debug.assert(program.redirectTargetsMap.has(fileName));
|
||||
const redirectTarget = program.getSourceFile(fileName);
|
||||
const redirect = find(sourceFilesToSearch, f => !!f.redirectInfo && f.redirectInfo.redirectTarget === redirectTarget)!;
|
||||
fileName = redirect.fileName;
|
||||
Debug.assert(sourceFilesSet.has(fileName));
|
||||
}
|
||||
return { fileName, highlightSpans };
|
||||
});
|
||||
});
|
||||
|
||||
return keywords;
|
||||
}
|
||||
|
||||
function getTryCatchFinallyOccurrences(tryStatement: TryStatement, sourceFile: SourceFile): Node[] {
|
||||
const keywords: Node[] = [];
|
||||
|
||||
pushKeywordIf(keywords, tryStatement.getFirstToken(), SyntaxKind.TryKeyword);
|
||||
|
||||
if (tryStatement.catchClause) {
|
||||
pushKeywordIf(keywords, tryStatement.catchClause.getFirstToken(), SyntaxKind.CatchKeyword);
|
||||
}
|
||||
|
||||
if (tryStatement.finallyBlock) {
|
||||
const finallyKeyword = findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile)!;
|
||||
pushKeywordIf(keywords, finallyKeyword, SyntaxKind.FinallyKeyword);
|
||||
function getSyntacticDocumentHighlights(node: Node, sourceFile: SourceFile): DocumentHighlights[] | undefined {
|
||||
const highlightSpans = getHighlightSpans(node, sourceFile);
|
||||
return highlightSpans && [{ fileName: sourceFile.fileName, highlightSpans }];
|
||||
}
|
||||
|
||||
return keywords;
|
||||
}
|
||||
function getHighlightSpans(node: Node, sourceFile: SourceFile): HighlightSpan[] | undefined {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.IfKeyword:
|
||||
case SyntaxKind.ElseKeyword:
|
||||
return isIfStatement(node.parent) ? getIfElseOccurrences(node.parent, sourceFile) : undefined;
|
||||
case SyntaxKind.ReturnKeyword:
|
||||
return useParent(node.parent, isReturnStatement, getReturnOccurrences);
|
||||
case SyntaxKind.ThrowKeyword:
|
||||
return useParent(node.parent, isThrowStatement, getThrowOccurrences);
|
||||
case SyntaxKind.TryKeyword:
|
||||
case SyntaxKind.CatchKeyword:
|
||||
case SyntaxKind.FinallyKeyword:
|
||||
const tryStatement = node.kind === SyntaxKind.CatchKeyword ? node.parent.parent : node.parent;
|
||||
return useParent(tryStatement, isTryStatement, getTryCatchFinallyOccurrences);
|
||||
case SyntaxKind.SwitchKeyword:
|
||||
return useParent(node.parent, isSwitchStatement, getSwitchCaseDefaultOccurrences);
|
||||
case SyntaxKind.CaseKeyword:
|
||||
case SyntaxKind.DefaultKeyword:
|
||||
return useParent(node.parent.parent.parent, isSwitchStatement, getSwitchCaseDefaultOccurrences);
|
||||
case SyntaxKind.BreakKeyword:
|
||||
case SyntaxKind.ContinueKeyword:
|
||||
return useParent(node.parent, isBreakOrContinueStatement, getBreakOrContinueStatementOccurrences);
|
||||
case SyntaxKind.ForKeyword:
|
||||
case SyntaxKind.WhileKeyword:
|
||||
case SyntaxKind.DoKeyword:
|
||||
return useParent(node.parent, (n): n is IterationStatement => isIterationStatement(n, /*lookInLabeledStatements*/ true), getLoopBreakContinueOccurrences);
|
||||
case SyntaxKind.ConstructorKeyword:
|
||||
return getFromAllDeclarations(isConstructorDeclaration, [SyntaxKind.ConstructorKeyword]);
|
||||
case SyntaxKind.GetKeyword:
|
||||
case SyntaxKind.SetKeyword:
|
||||
return getFromAllDeclarations(isAccessor, [SyntaxKind.GetKeyword, SyntaxKind.SetKeyword]);
|
||||
case SyntaxKind.AwaitKeyword:
|
||||
return useParent(node.parent, isAwaitExpression, getAsyncAndAwaitOccurrences);
|
||||
case SyntaxKind.AsyncKeyword:
|
||||
return highlightSpans(getAsyncAndAwaitOccurrences(node));
|
||||
case SyntaxKind.YieldKeyword:
|
||||
return highlightSpans(getYieldOccurrences(node));
|
||||
default:
|
||||
return isModifierKind(node.kind) && (isDeclaration(node.parent) || isVariableStatement(node.parent))
|
||||
? highlightSpans(getModifierOccurrences(node.kind, node.parent))
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function getThrowOccurrences(throwStatement: ThrowStatement, sourceFile: SourceFile): Node[] | undefined {
|
||||
const owner = getThrowStatementOwner(throwStatement);
|
||||
function getFromAllDeclarations<T extends Node>(nodeTest: (node: Node) => node is T, keywords: readonly SyntaxKind[]): HighlightSpan[] | undefined {
|
||||
return useParent(node.parent, nodeTest, decl => mapDefined(decl.symbol.declarations, d =>
|
||||
nodeTest(d) ? find(d.getChildren(sourceFile), c => contains(keywords, c.kind)) : undefined));
|
||||
}
|
||||
|
||||
function useParent<T extends Node>(node: Node, nodeTest: (node: Node) => node is T, getNodes: (node: T, sourceFile: SourceFile) => readonly Node[] | undefined): HighlightSpan[] | undefined {
|
||||
return nodeTest(node) ? highlightSpans(getNodes(node, sourceFile)) : undefined;
|
||||
}
|
||||
|
||||
function highlightSpans(nodes: readonly Node[] | undefined): HighlightSpan[] | undefined {
|
||||
return nodes && nodes.map(node => getHighlightSpanForNode(node, sourceFile));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregates all throw-statements within this node *without* crossing
|
||||
* into function boundaries and try-blocks with catch-clauses.
|
||||
*/
|
||||
function aggregateOwnedThrowStatements(node: Node): readonly ThrowStatement[] | undefined {
|
||||
if (isThrowStatement(node)) {
|
||||
return [node];
|
||||
}
|
||||
else if (isTryStatement(node)) {
|
||||
// Exceptions thrown within a try block lacking a catch clause are "owned" in the current context.
|
||||
return concatenate(
|
||||
node.catchClause ? aggregateOwnedThrowStatements(node.catchClause) : node.tryBlock && aggregateOwnedThrowStatements(node.tryBlock),
|
||||
node.finallyBlock && aggregateOwnedThrowStatements(node.finallyBlock));
|
||||
}
|
||||
// Do not cross function boundaries.
|
||||
return isFunctionLike(node) ? undefined : flatMapChildren(node, aggregateOwnedThrowStatements);
|
||||
}
|
||||
|
||||
/**
|
||||
* For lack of a better name, this function takes a throw statement and returns the
|
||||
* nearest ancestor that is a try-block (whose try statement has a catch clause),
|
||||
* function-block, or source file.
|
||||
*/
|
||||
function getThrowStatementOwner(throwStatement: ThrowStatement): Node | undefined {
|
||||
let child: Node = throwStatement;
|
||||
|
||||
while (child.parent) {
|
||||
const parent = child.parent;
|
||||
|
||||
if (isFunctionBlock(parent) || parent.kind === SyntaxKind.SourceFile) {
|
||||
return parent;
|
||||
}
|
||||
|
||||
// A throw-statement is only owned by a try-statement if the try-statement has
|
||||
// a catch clause, and if the throw-statement occurs within the try block.
|
||||
if (isTryStatement(parent) && parent.tryBlock === child && parent.catchClause) {
|
||||
return child;
|
||||
}
|
||||
|
||||
child = parent;
|
||||
}
|
||||
|
||||
if (!owner) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const keywords: Node[] = [];
|
||||
function aggregateAllBreakAndContinueStatements(node: Node): readonly BreakOrContinueStatement[] | undefined {
|
||||
return isBreakOrContinueStatement(node) ? [node] : isFunctionLike(node) ? undefined : flatMapChildren(node, aggregateAllBreakAndContinueStatements);
|
||||
}
|
||||
|
||||
forEach(aggregateOwnedThrowStatements(owner), throwStatement => {
|
||||
keywords.push(findChildOfKind(throwStatement, SyntaxKind.ThrowKeyword, sourceFile)!);
|
||||
});
|
||||
function flatMapChildren<T>(node: Node, cb: (child: Node) => readonly T[] | T | undefined): readonly T[] {
|
||||
const result: T[] = [];
|
||||
node.forEachChild(child => {
|
||||
const value = cb(child);
|
||||
if (value !== undefined) {
|
||||
result.push(...toArray(value));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// If the "owner" is a function, then we equate 'return' and 'throw' statements in their
|
||||
// ability to "jump out" of the function, and include occurrences for both.
|
||||
if (isFunctionBlock(owner)) {
|
||||
forEachReturnStatement(<Block>owner, returnStatement => {
|
||||
function ownsBreakOrContinueStatement(owner: Node, statement: BreakOrContinueStatement): boolean {
|
||||
const actualOwner = getBreakOrContinueOwner(statement);
|
||||
return !!actualOwner && actualOwner === owner;
|
||||
}
|
||||
|
||||
function getBreakOrContinueOwner(statement: BreakOrContinueStatement): Node | undefined {
|
||||
return findAncestor(statement, node => {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.SwitchStatement:
|
||||
if (statement.kind === SyntaxKind.ContinueStatement) {
|
||||
return false;
|
||||
}
|
||||
// falls through
|
||||
|
||||
case SyntaxKind.ForStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.ForOfStatement:
|
||||
case SyntaxKind.WhileStatement:
|
||||
case SyntaxKind.DoStatement:
|
||||
return !statement.label || isLabeledBy(node, statement.label.escapedText);
|
||||
default:
|
||||
// Don't cross function boundaries.
|
||||
// TODO: GH#20090
|
||||
return isFunctionLike(node) && "quit";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getModifierOccurrences(modifier: Modifier["kind"], declaration: Node): Node[] {
|
||||
return mapDefined(getNodesToSearchForModifier(declaration, modifierToFlag(modifier)), node => findModifier(node, modifier));
|
||||
}
|
||||
|
||||
function getNodesToSearchForModifier(declaration: Node, modifierFlag: ModifierFlags): readonly Node[] | undefined {
|
||||
// Types of node whose children might have modifiers.
|
||||
const container = declaration.parent as ModuleBlock | SourceFile | Block | CaseClause | DefaultClause | ConstructorDeclaration | MethodDeclaration | FunctionDeclaration | ObjectTypeDeclaration;
|
||||
switch (container.kind) {
|
||||
case SyntaxKind.ModuleBlock:
|
||||
case SyntaxKind.SourceFile:
|
||||
case SyntaxKind.Block:
|
||||
case SyntaxKind.CaseClause:
|
||||
case SyntaxKind.DefaultClause:
|
||||
// Container is either a class declaration or the declaration is a classDeclaration
|
||||
if (modifierFlag & ModifierFlags.Abstract && isClassDeclaration(declaration)) {
|
||||
return [...declaration.members, declaration];
|
||||
}
|
||||
else {
|
||||
return container.statements;
|
||||
}
|
||||
case SyntaxKind.Constructor:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
return [...container.parameters, ...(isClassLike(container.parent) ? container.parent.members : [])];
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.ClassExpression:
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
case SyntaxKind.TypeLiteral:
|
||||
const nodes = container.members;
|
||||
|
||||
// If we're an accessibility modifier, we're in an instance member and should search
|
||||
// the constructor's parameter list for instance members as well.
|
||||
if (modifierFlag & (ModifierFlags.AccessibilityModifier | ModifierFlags.Readonly)) {
|
||||
const constructor = find(container.members, isConstructorDeclaration);
|
||||
if (constructor) {
|
||||
return [...nodes, ...constructor.parameters];
|
||||
}
|
||||
}
|
||||
else if (modifierFlag & ModifierFlags.Abstract) {
|
||||
return [...nodes, container];
|
||||
}
|
||||
return nodes;
|
||||
default:
|
||||
Debug.assertNever(container, "Invalid container kind.");
|
||||
}
|
||||
}
|
||||
|
||||
function pushKeywordIf(keywordList: Push<Node>, token: Node | undefined, ...expected: SyntaxKind[]): boolean {
|
||||
if (token && contains(expected, token.kind)) {
|
||||
keywordList.push(token);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getLoopBreakContinueOccurrences(loopNode: IterationStatement): Node[] {
|
||||
const keywords: Node[] = [];
|
||||
|
||||
if (pushKeywordIf(keywords, loopNode.getFirstToken(), SyntaxKind.ForKeyword, SyntaxKind.WhileKeyword, SyntaxKind.DoKeyword)) {
|
||||
// If we succeeded and got a do-while loop, then start looking for a 'while' keyword.
|
||||
if (loopNode.kind === SyntaxKind.DoStatement) {
|
||||
const loopTokens = loopNode.getChildren();
|
||||
|
||||
for (let i = loopTokens.length - 1; i >= 0; i--) {
|
||||
if (pushKeywordIf(keywords, loopTokens[i], SyntaxKind.WhileKeyword)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
forEach(aggregateAllBreakAndContinueStatements(loopNode.statement), statement => {
|
||||
if (ownsBreakOrContinueStatement(loopNode, statement)) {
|
||||
pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword);
|
||||
}
|
||||
});
|
||||
|
||||
return keywords;
|
||||
}
|
||||
|
||||
function getBreakOrContinueStatementOccurrences(breakOrContinueStatement: BreakOrContinueStatement): Node[] | undefined {
|
||||
const owner = getBreakOrContinueOwner(breakOrContinueStatement);
|
||||
|
||||
if (owner) {
|
||||
switch (owner.kind) {
|
||||
case SyntaxKind.ForStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.ForOfStatement:
|
||||
case SyntaxKind.DoStatement:
|
||||
case SyntaxKind.WhileStatement:
|
||||
return getLoopBreakContinueOccurrences(<IterationStatement>owner);
|
||||
case SyntaxKind.SwitchStatement:
|
||||
return getSwitchCaseDefaultOccurrences(<SwitchStatement>owner);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getSwitchCaseDefaultOccurrences(switchStatement: SwitchStatement): Node[] {
|
||||
const keywords: Node[] = [];
|
||||
|
||||
pushKeywordIf(keywords, switchStatement.getFirstToken(), SyntaxKind.SwitchKeyword);
|
||||
|
||||
// Go through each clause in the switch statement, collecting the 'case'/'default' keywords.
|
||||
forEach(switchStatement.caseBlock.clauses, clause => {
|
||||
pushKeywordIf(keywords, clause.getFirstToken(), SyntaxKind.CaseKeyword, SyntaxKind.DefaultKeyword);
|
||||
|
||||
forEach(aggregateAllBreakAndContinueStatements(clause), statement => {
|
||||
if (ownsBreakOrContinueStatement(switchStatement, statement)) {
|
||||
pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return keywords;
|
||||
}
|
||||
|
||||
function getTryCatchFinallyOccurrences(tryStatement: TryStatement, sourceFile: SourceFile): Node[] {
|
||||
const keywords: Node[] = [];
|
||||
|
||||
pushKeywordIf(keywords, tryStatement.getFirstToken(), SyntaxKind.TryKeyword);
|
||||
|
||||
if (tryStatement.catchClause) {
|
||||
pushKeywordIf(keywords, tryStatement.catchClause.getFirstToken(), SyntaxKind.CatchKeyword);
|
||||
}
|
||||
|
||||
if (tryStatement.finallyBlock) {
|
||||
const finallyKeyword = findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile)!;
|
||||
pushKeywordIf(keywords, finallyKeyword, SyntaxKind.FinallyKeyword);
|
||||
}
|
||||
|
||||
return keywords;
|
||||
}
|
||||
|
||||
function getThrowOccurrences(throwStatement: ThrowStatement, sourceFile: SourceFile): Node[] | undefined {
|
||||
const owner = getThrowStatementOwner(throwStatement);
|
||||
|
||||
if (!owner) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const keywords: Node[] = [];
|
||||
|
||||
forEach(aggregateOwnedThrowStatements(owner), throwStatement => {
|
||||
keywords.push(findChildOfKind(throwStatement, SyntaxKind.ThrowKeyword, sourceFile)!);
|
||||
});
|
||||
|
||||
// If the "owner" is a function, then we equate 'return' and 'throw' statements in their
|
||||
// ability to "jump out" of the function, and include occurrences for both.
|
||||
if (isFunctionBlock(owner)) {
|
||||
forEachReturnStatement(<Block>owner, returnStatement => {
|
||||
keywords.push(findChildOfKind(returnStatement, SyntaxKind.ReturnKeyword, sourceFile)!);
|
||||
});
|
||||
}
|
||||
|
||||
return keywords;
|
||||
}
|
||||
|
||||
function getReturnOccurrences(returnStatement: ReturnStatement, sourceFile: SourceFile): Node[] | undefined {
|
||||
const func = <FunctionLikeDeclaration>getContainingFunction(returnStatement);
|
||||
if (!func) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const keywords: Node[] = [];
|
||||
forEachReturnStatement(cast(func.body, isBlock), returnStatement => {
|
||||
keywords.push(findChildOfKind(returnStatement, SyntaxKind.ReturnKeyword, sourceFile)!);
|
||||
});
|
||||
}
|
||||
|
||||
return keywords;
|
||||
}
|
||||
|
||||
function getReturnOccurrences(returnStatement: ReturnStatement, sourceFile: SourceFile): Node[] | undefined {
|
||||
const func = <FunctionLikeDeclaration>getContainingFunction(returnStatement);
|
||||
if (!func) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const keywords: Node[] = [];
|
||||
forEachReturnStatement(cast(func.body, isBlock), returnStatement => {
|
||||
keywords.push(findChildOfKind(returnStatement, SyntaxKind.ReturnKeyword, sourceFile)!);
|
||||
});
|
||||
|
||||
// Include 'throw' statements that do not occur within a try block.
|
||||
forEach(aggregateOwnedThrowStatements(func.body!), throwStatement => {
|
||||
keywords.push(findChildOfKind(throwStatement, SyntaxKind.ThrowKeyword, sourceFile)!);
|
||||
});
|
||||
|
||||
return keywords;
|
||||
}
|
||||
|
||||
function getAsyncAndAwaitOccurrences(node: Node): Node[] | undefined {
|
||||
const func = <FunctionLikeDeclaration>getContainingFunction(node);
|
||||
if (!func) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const keywords: Node[] = [];
|
||||
|
||||
if (func.modifiers) {
|
||||
func.modifiers.forEach(modifier => {
|
||||
pushKeywordIf(keywords, modifier, SyntaxKind.AsyncKeyword);
|
||||
// Include 'throw' statements that do not occur within a try block.
|
||||
forEach(aggregateOwnedThrowStatements(func.body!), throwStatement => {
|
||||
keywords.push(findChildOfKind(throwStatement, SyntaxKind.ThrowKeyword, sourceFile)!);
|
||||
});
|
||||
|
||||
return keywords;
|
||||
}
|
||||
|
||||
forEachChild(func, child => {
|
||||
traverseWithoutCrossingFunction(child, node => {
|
||||
if (isAwaitExpression(node)) {
|
||||
pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.AwaitKeyword);
|
||||
function getAsyncAndAwaitOccurrences(node: Node): Node[] | undefined {
|
||||
const func = <FunctionLikeDeclaration>getContainingFunction(node);
|
||||
if (!func) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const keywords: Node[] = [];
|
||||
|
||||
if (func.modifiers) {
|
||||
func.modifiers.forEach(modifier => {
|
||||
pushKeywordIf(keywords, modifier, SyntaxKind.AsyncKeyword);
|
||||
});
|
||||
}
|
||||
|
||||
forEachChild(func, child => {
|
||||
traverseWithoutCrossingFunction(child, node => {
|
||||
if (isAwaitExpression(node)) {
|
||||
pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.AwaitKeyword);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
return keywords;
|
||||
}
|
||||
|
||||
function getYieldOccurrences(node: Node): Node[] | undefined {
|
||||
const func = getContainingFunction(node) as FunctionDeclaration;
|
||||
if (!func) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const keywords: Node[] = [];
|
||||
|
||||
forEachChild(func, child => {
|
||||
traverseWithoutCrossingFunction(child, node => {
|
||||
if (isYieldExpression(node)) {
|
||||
pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.YieldKeyword);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return keywords;
|
||||
}
|
||||
|
||||
// Do not cross function/class/interface/module/type boundaries.
|
||||
function traverseWithoutCrossingFunction(node: Node, cb: (node: Node) => void) {
|
||||
cb(node);
|
||||
if (!isFunctionLike(node) && !isClassLike(node) && !isInterfaceDeclaration(node) && !isModuleDeclaration(node) && !isTypeAliasDeclaration(node) && !isTypeNode(node)) {
|
||||
forEachChild(node, child => traverseWithoutCrossingFunction(child, cb));
|
||||
}
|
||||
}
|
||||
|
||||
function getIfElseOccurrences(ifStatement: IfStatement, sourceFile: SourceFile): HighlightSpan[] {
|
||||
const keywords = getIfElseKeywords(ifStatement, sourceFile);
|
||||
const result: HighlightSpan[] = [];
|
||||
|
||||
// We'd like to highlight else/ifs together if they are only separated by whitespace
|
||||
// (i.e. the keywords are separated by no comments, no newlines).
|
||||
for (let i = 0; i < keywords.length; i++) {
|
||||
if (keywords[i].kind === SyntaxKind.ElseKeyword && i < keywords.length - 1) {
|
||||
const elseKeyword = keywords[i];
|
||||
const ifKeyword = keywords[i + 1]; // this *should* always be an 'if' keyword.
|
||||
|
||||
let shouldCombineElseAndIf = true;
|
||||
|
||||
// Avoid recalculating getStart() by iterating backwards.
|
||||
for (let j = ifKeyword.getStart(sourceFile) - 1; j >= elseKeyword.end; j--) {
|
||||
if (!isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(j))) {
|
||||
shouldCombineElseAndIf = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldCombineElseAndIf) {
|
||||
result.push({
|
||||
fileName: sourceFile.fileName,
|
||||
textSpan: createTextSpanFromBounds(elseKeyword.getStart(), ifKeyword.end),
|
||||
kind: HighlightSpanKind.reference
|
||||
});
|
||||
i++; // skip the next keyword
|
||||
continue;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Ordinary case: just highlight the keyword.
|
||||
result.push(getHighlightSpanForNode(keywords[i], sourceFile));
|
||||
}
|
||||
|
||||
return keywords;
|
||||
}
|
||||
|
||||
function getYieldOccurrences(node: Node): Node[] | undefined {
|
||||
const func = getContainingFunction(node) as FunctionDeclaration;
|
||||
if (!func) {
|
||||
return undefined;
|
||||
return result;
|
||||
}
|
||||
|
||||
const keywords: Node[] = [];
|
||||
function getIfElseKeywords(ifStatement: IfStatement, sourceFile: SourceFile): Node[] {
|
||||
const keywords: Node[] = [];
|
||||
|
||||
forEachChild(func, child => {
|
||||
traverseWithoutCrossingFunction(child, node => {
|
||||
if (isYieldExpression(node)) {
|
||||
pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.YieldKeyword);
|
||||
}
|
||||
});
|
||||
});
|
||||
// Traverse upwards through all parent if-statements linked by their else-branches.
|
||||
while (isIfStatement(ifStatement.parent) && ifStatement.parent.elseStatement === ifStatement) {
|
||||
ifStatement = ifStatement.parent;
|
||||
}
|
||||
|
||||
return keywords;
|
||||
}
|
||||
// Now traverse back down through the else branches, aggregating if/else keywords of if-statements.
|
||||
while (true) {
|
||||
const children = ifStatement.getChildren(sourceFile);
|
||||
pushKeywordIf(keywords, children[0], SyntaxKind.IfKeyword);
|
||||
|
||||
// Do not cross function/class/interface/module/type boundaries.
|
||||
function traverseWithoutCrossingFunction(node: Node, cb: (node: Node) => void) {
|
||||
cb(node);
|
||||
if (!isFunctionLike(node) && !isClassLike(node) && !isInterfaceDeclaration(node) && !isModuleDeclaration(node) && !isTypeAliasDeclaration(node) && !isTypeNode(node)) {
|
||||
forEachChild(node, child => traverseWithoutCrossingFunction(child, cb));
|
||||
}
|
||||
}
|
||||
|
||||
function getIfElseOccurrences(ifStatement: IfStatement, sourceFile: SourceFile): HighlightSpan[] {
|
||||
const keywords = getIfElseKeywords(ifStatement, sourceFile);
|
||||
const result: HighlightSpan[] = [];
|
||||
|
||||
// We'd like to highlight else/ifs together if they are only separated by whitespace
|
||||
// (i.e. the keywords are separated by no comments, no newlines).
|
||||
for (let i = 0; i < keywords.length; i++) {
|
||||
if (keywords[i].kind === SyntaxKind.ElseKeyword && i < keywords.length - 1) {
|
||||
const elseKeyword = keywords[i];
|
||||
const ifKeyword = keywords[i + 1]; // this *should* always be an 'if' keyword.
|
||||
|
||||
let shouldCombineElseAndIf = true;
|
||||
|
||||
// Avoid recalculating getStart() by iterating backwards.
|
||||
for (let j = ifKeyword.getStart(sourceFile) - 1; j >= elseKeyword.end; j--) {
|
||||
if (!isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(j))) {
|
||||
shouldCombineElseAndIf = false;
|
||||
// Generally the 'else' keyword is second-to-last, so we traverse backwards.
|
||||
for (let i = children.length - 1; i >= 0; i--) {
|
||||
if (pushKeywordIf(keywords, children[i], SyntaxKind.ElseKeyword)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldCombineElseAndIf) {
|
||||
result.push({
|
||||
fileName: sourceFile.fileName,
|
||||
textSpan: createTextSpanFromBounds(elseKeyword.getStart(), ifKeyword.end),
|
||||
kind: HighlightSpanKind.reference
|
||||
});
|
||||
i++; // skip the next keyword
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Ordinary case: just highlight the keyword.
|
||||
result.push(getHighlightSpanForNode(keywords[i], sourceFile));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function getIfElseKeywords(ifStatement: IfStatement, sourceFile: SourceFile): Node[] {
|
||||
const keywords: Node[] = [];
|
||||
|
||||
// Traverse upwards through all parent if-statements linked by their else-branches.
|
||||
while (isIfStatement(ifStatement.parent) && ifStatement.parent.elseStatement === ifStatement) {
|
||||
ifStatement = ifStatement.parent;
|
||||
}
|
||||
|
||||
// Now traverse back down through the else branches, aggregating if/else keywords of if-statements.
|
||||
while (true) {
|
||||
const children = ifStatement.getChildren(sourceFile);
|
||||
pushKeywordIf(keywords, children[0], SyntaxKind.IfKeyword);
|
||||
|
||||
// Generally the 'else' keyword is second-to-last, so we traverse backwards.
|
||||
for (let i = children.length - 1; i >= 0; i--) {
|
||||
if (pushKeywordIf(keywords, children[i], SyntaxKind.ElseKeyword)) {
|
||||
if (!ifStatement.elseStatement || !isIfStatement(ifStatement.elseStatement)) {
|
||||
break;
|
||||
}
|
||||
|
||||
ifStatement = ifStatement.elseStatement;
|
||||
}
|
||||
|
||||
if (!ifStatement.elseStatement || !isIfStatement(ifStatement.elseStatement)) {
|
||||
break;
|
||||
}
|
||||
|
||||
ifStatement = ifStatement.elseStatement;
|
||||
return keywords;
|
||||
}
|
||||
|
||||
return keywords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not a 'node' is preceded by a label of the given string.
|
||||
* Note: 'node' cannot be a SourceFile.
|
||||
*/
|
||||
function isLabeledBy(node: Node, labelName: __String): boolean {
|
||||
return !!findAncestor(node.parent, owner => !isLabeledStatement(owner) ? "quit" : owner.label.escapedText === labelName);
|
||||
/**
|
||||
* Whether or not a 'node' is preceded by a label of the given string.
|
||||
* Note: 'node' cannot be a SourceFile.
|
||||
*/
|
||||
function isLabeledBy(node: Node, labelName: __String): boolean {
|
||||
return !!findAncestor(node.parent, owner => !isLabeledStatement(owner) ? "quit" : owner.label.escapedText === labelName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,10 @@
|
||||
// https://mathiasbynens.be/notes/globalthis
|
||||
|
||||
// #region The polyfill starts here.
|
||||
/* eslint-disable no-var */
|
||||
/* @internal */
|
||||
declare var window: {};
|
||||
/* eslint-enable no-var */
|
||||
((() => {
|
||||
if (typeof globalThis === "object") return;
|
||||
try {
|
||||
|
||||
@ -1,44 +1,21 @@
|
||||
/* @internal */
|
||||
namespace ts {
|
||||
export interface Refactor {
|
||||
/** Compute the associated code actions */
|
||||
getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined;
|
||||
namespace ts.refactor {
|
||||
// A map with the refactor code as key, the refactor itself as value
|
||||
// e.g. nonSuggestableRefactors[refactorCode] -> the refactor you want
|
||||
const refactors: Map<Refactor> = createMap<Refactor>();
|
||||
|
||||
/** Compute (quickly) which actions are available here */
|
||||
getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[];
|
||||
/** @param name An unique code associated with each refactor. Does not have to be human-readable. */
|
||||
export function registerRefactor(name: string, refactor: Refactor) {
|
||||
refactors.set(name, refactor);
|
||||
}
|
||||
|
||||
export interface RefactorContext extends textChanges.TextChangesContext {
|
||||
file: SourceFile;
|
||||
startPosition: number;
|
||||
endPosition?: number;
|
||||
program: Program;
|
||||
cancellationToken?: CancellationToken;
|
||||
preferences: UserPreferences;
|
||||
export function getApplicableRefactors(context: RefactorContext): ApplicableRefactorInfo[] {
|
||||
return arrayFrom(flatMapIterator(refactors.values(), refactor =>
|
||||
context.cancellationToken && context.cancellationToken.isCancellationRequested() ? undefined : refactor.getAvailableActions(context)));
|
||||
}
|
||||
|
||||
export namespace refactor {
|
||||
// A map with the refactor code as key, the refactor itself as value
|
||||
// e.g. nonSuggestableRefactors[refactorCode] -> the refactor you want
|
||||
const refactors: Map<Refactor> = createMap<Refactor>();
|
||||
|
||||
/** @param name An unique code associated with each refactor. Does not have to be human-readable. */
|
||||
export function registerRefactor(name: string, refactor: Refactor) {
|
||||
refactors.set(name, refactor);
|
||||
}
|
||||
|
||||
export function getApplicableRefactors(context: RefactorContext): ApplicableRefactorInfo[] {
|
||||
return arrayFrom(flatMapIterator(refactors.values(), refactor =>
|
||||
context.cancellationToken && context.cancellationToken.isCancellationRequested() ? undefined : refactor.getAvailableActions(context)));
|
||||
}
|
||||
|
||||
export function getEditsForRefactor(context: RefactorContext, refactorName: string, actionName: string): RefactorEditInfo | undefined {
|
||||
const refactor = refactors.get(refactorName);
|
||||
return refactor && refactor.getEditsForAction(context, actionName);
|
||||
}
|
||||
}
|
||||
|
||||
export function getRefactorContextSpan({ startPosition, endPosition }: RefactorContext): TextSpan {
|
||||
return createTextSpanFromBounds(startPosition, endPosition === undefined ? startPosition : endPosition);
|
||||
export function getEditsForRefactor(context: RefactorContext, refactorName: string, actionName: string): RefactorEditInfo | undefined {
|
||||
const refactor = refactors.get(refactorName);
|
||||
return refactor && refactor.getEditsForAction(context, actionName);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2321,5 +2321,5 @@ namespace ts {
|
||||
throw new Error("getDefaultLibFilePath is only supported when consumed as a node module. ");
|
||||
}
|
||||
|
||||
objectAllocator = getServicesObjectAllocator();
|
||||
setObjectAllocator(getServicesObjectAllocator());
|
||||
}
|
||||
|
||||
@ -677,11 +677,6 @@ namespace ts {
|
||||
displayParts: SymbolDisplayPart[];
|
||||
}
|
||||
|
||||
export interface DocumentHighlights {
|
||||
fileName: string;
|
||||
highlightSpans: HighlightSpan[];
|
||||
}
|
||||
|
||||
export const enum HighlightSpanKind {
|
||||
none = "none",
|
||||
definition = "definition",
|
||||
@ -1254,4 +1249,50 @@ namespace ts {
|
||||
jsxAttributeStringLiteralValue = 24,
|
||||
bigintLiteral = 25,
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface CodeFixRegistration {
|
||||
errorCodes: readonly number[];
|
||||
getCodeActions(context: CodeFixContext): CodeFixAction[] | undefined;
|
||||
fixIds?: readonly string[];
|
||||
getAllCodeActions?(context: CodeFixAllContext): CombinedCodeActions;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface CodeFixContextBase extends textChanges.TextChangesContext {
|
||||
sourceFile: SourceFile;
|
||||
program: Program;
|
||||
cancellationToken: CancellationToken;
|
||||
preferences: UserPreferences;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface CodeFixAllContext extends CodeFixContextBase {
|
||||
fixId: {};
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface CodeFixContext extends CodeFixContextBase {
|
||||
errorCode: number;
|
||||
span: TextSpan;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface Refactor {
|
||||
/** Compute the associated code actions */
|
||||
getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined;
|
||||
|
||||
/** Compute (quickly) which actions are available here */
|
||||
getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface RefactorContext extends textChanges.TextChangesContext {
|
||||
file: SourceFile;
|
||||
startPosition: number;
|
||||
endPosition?: number;
|
||||
program: Program;
|
||||
cancellationToken?: CancellationToken;
|
||||
preferences: UserPreferences;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2292,4 +2292,9 @@ namespace ts {
|
||||
return contains(getPathComponents(fileOrDirectory), "node_modules");
|
||||
}
|
||||
// #endregion
|
||||
|
||||
/* @internal */
|
||||
export function getRefactorContextSpan({ startPosition, endPosition }: RefactorContext): TextSpan {
|
||||
return createTextSpanFromBounds(startPosition, endPosition === undefined ? startPosition : endPosition);
|
||||
}
|
||||
}
|
||||
@ -5308,10 +5308,6 @@ declare namespace ts {
|
||||
kind: ScriptElementKind;
|
||||
displayParts: SymbolDisplayPart[];
|
||||
}
|
||||
interface DocumentHighlights {
|
||||
fileName: string;
|
||||
highlightSpans: HighlightSpan[];
|
||||
}
|
||||
enum HighlightSpanKind {
|
||||
none = "none",
|
||||
definition = "definition",
|
||||
@ -5780,6 +5776,12 @@ declare namespace ts {
|
||||
/** The classifier is used for syntactic highlighting in editors via the TSServer */
|
||||
function createClassifier(): Classifier;
|
||||
}
|
||||
declare namespace ts {
|
||||
interface DocumentHighlights {
|
||||
fileName: string;
|
||||
highlightSpans: HighlightSpan[];
|
||||
}
|
||||
}
|
||||
declare namespace ts {
|
||||
/**
|
||||
* The document registry represents a store of SourceFile objects that can be shared between
|
||||
|
||||
10
tests/baselines/reference/api/typescript.d.ts
vendored
10
tests/baselines/reference/api/typescript.d.ts
vendored
@ -5308,10 +5308,6 @@ declare namespace ts {
|
||||
kind: ScriptElementKind;
|
||||
displayParts: SymbolDisplayPart[];
|
||||
}
|
||||
interface DocumentHighlights {
|
||||
fileName: string;
|
||||
highlightSpans: HighlightSpan[];
|
||||
}
|
||||
enum HighlightSpanKind {
|
||||
none = "none",
|
||||
definition = "definition",
|
||||
@ -5780,6 +5776,12 @@ declare namespace ts {
|
||||
/** The classifier is used for syntactic highlighting in editors via the TSServer */
|
||||
function createClassifier(): Classifier;
|
||||
}
|
||||
declare namespace ts {
|
||||
interface DocumentHighlights {
|
||||
fileName: string;
|
||||
highlightSpans: HighlightSpan[];
|
||||
}
|
||||
}
|
||||
declare namespace ts {
|
||||
/**
|
||||
* The document registry represents a store of SourceFile objects that can be shared between
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user