mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge branch 'master' into fix-implement-interface-with-extends-class
This commit is contained in:
@@ -14,7 +14,8 @@ namespace ts.codefix {
|
||||
registerCodeFix({
|
||||
errorCodes,
|
||||
getCodeActions(context) {
|
||||
const { errorCode, sourceFile } = context;
|
||||
const { errorCode, sourceFile, program } = context;
|
||||
const checker = program.getTypeChecker();
|
||||
const startToken = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false);
|
||||
|
||||
const importDecl = tryGetFullImport(startToken);
|
||||
@@ -22,7 +23,7 @@ namespace ts.codefix {
|
||||
const changes = textChanges.ChangeTracker.with(context, t => t.deleteNode(sourceFile, importDecl));
|
||||
return [createCodeFixAction(fixName, changes, [Diagnostics.Remove_import_from_0, showModuleSpecifier(importDecl)], fixIdDelete, Diagnostics.Delete_all_unused_declarations)];
|
||||
}
|
||||
const delDestructure = textChanges.ChangeTracker.with(context, t => tryDeleteFullDestructure(t, sourceFile, startToken, /*deleted*/ undefined));
|
||||
const delDestructure = textChanges.ChangeTracker.with(context, t => tryDeleteFullDestructure(t, sourceFile, startToken, /*deleted*/ undefined, checker, /*isFixAll*/ false));
|
||||
if (delDestructure.length) {
|
||||
return [createCodeFixAction(fixName, delDestructure, Diagnostics.Remove_destructuring, fixIdDelete, Diagnostics.Delete_all_unused_declarations)];
|
||||
}
|
||||
@@ -34,7 +35,7 @@ namespace ts.codefix {
|
||||
const token = getToken(sourceFile, textSpanEnd(context.span));
|
||||
const result: CodeFixAction[] = [];
|
||||
|
||||
const deletion = textChanges.ChangeTracker.with(context, t => tryDeleteDeclaration(t, sourceFile, token, /*deleted*/ undefined));
|
||||
const deletion = textChanges.ChangeTracker.with(context, t => tryDeleteDeclaration(t, sourceFile, token, /*deleted*/ undefined, checker, /*isFixAll*/ false));
|
||||
if (deletion.length) {
|
||||
result.push(createCodeFixAction(fixName, deletion, [Diagnostics.Remove_declaration_for_Colon_0, token.getText(sourceFile)], fixIdDelete, Diagnostics.Delete_all_unused_declarations));
|
||||
}
|
||||
@@ -50,8 +51,9 @@ namespace ts.codefix {
|
||||
getAllCodeActions: context => {
|
||||
// Track a set of deleted nodes that may be ancestors of other marked for deletion -- only delete the ancestors.
|
||||
const deleted = new NodeSet();
|
||||
const { sourceFile, program } = context;
|
||||
const checker = program.getTypeChecker();
|
||||
return codeFixAll(context, errorCodes, (changes, diag) => {
|
||||
const { sourceFile } = context;
|
||||
const startToken = getTokenAtPosition(sourceFile, diag.start, /*includeJsDocComment*/ false);
|
||||
const token = findPrecedingToken(textSpanEnd(diag), diag.file)!;
|
||||
switch (context.fixId) {
|
||||
@@ -68,8 +70,9 @@ namespace ts.codefix {
|
||||
if (importDecl) {
|
||||
changes.deleteNode(sourceFile, importDecl);
|
||||
}
|
||||
else if (!tryDeleteFullDestructure(changes, sourceFile, startToken, deleted) && !tryDeleteFullVariableStatement(changes, sourceFile, startToken, deleted)) {
|
||||
tryDeleteDeclaration(changes, sourceFile, token, deleted);
|
||||
else if (!tryDeleteFullDestructure(changes, sourceFile, startToken, deleted, checker, /*isFixAll*/ true) &&
|
||||
!tryDeleteFullVariableStatement(changes, sourceFile, startToken, deleted)) {
|
||||
tryDeleteDeclaration(changes, sourceFile, token, deleted, checker, /*isFixAll*/ true);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -84,7 +87,7 @@ namespace ts.codefix {
|
||||
return startToken.kind === SyntaxKind.ImportKeyword ? tryCast(startToken.parent, isImportDeclaration) : undefined;
|
||||
}
|
||||
|
||||
function tryDeleteFullDestructure(changes: textChanges.ChangeTracker, sourceFile: SourceFile, startToken: Node, deletedAncestors: NodeSet | undefined): boolean {
|
||||
function tryDeleteFullDestructure(changes: textChanges.ChangeTracker, sourceFile: SourceFile, startToken: Node, deletedAncestors: NodeSet | undefined, checker: TypeChecker, isFixAll: boolean): boolean {
|
||||
if (startToken.kind !== SyntaxKind.OpenBraceToken || !isObjectBindingPattern(startToken.parent)) return false;
|
||||
const decl = cast(startToken.parent, isObjectBindingPattern).parent;
|
||||
switch (decl.kind) {
|
||||
@@ -92,6 +95,7 @@ namespace ts.codefix {
|
||||
tryDeleteVariableDeclaration(changes, sourceFile, decl, deletedAncestors);
|
||||
break;
|
||||
case SyntaxKind.Parameter:
|
||||
if (!mayDeleteParameter(decl, checker, isFixAll)) break;
|
||||
if (deletedAncestors) deletedAncestors.add(decl);
|
||||
changes.deleteNodeInList(sourceFile, decl);
|
||||
break;
|
||||
@@ -144,10 +148,10 @@ namespace ts.codefix {
|
||||
return false;
|
||||
}
|
||||
|
||||
function tryDeleteDeclaration(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node, deletedAncestors: NodeSet | undefined): void {
|
||||
function tryDeleteDeclaration(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node, deletedAncestors: NodeSet | undefined, checker: TypeChecker, isFixAll: boolean): void {
|
||||
switch (token.kind) {
|
||||
case SyntaxKind.Identifier:
|
||||
tryDeleteIdentifier(changes, sourceFile, <Identifier>token, deletedAncestors);
|
||||
tryDeleteIdentifier(changes, sourceFile, <Identifier>token, deletedAncestors, checker, isFixAll);
|
||||
break;
|
||||
case SyntaxKind.PropertyDeclaration:
|
||||
case SyntaxKind.NamespaceImport:
|
||||
@@ -170,7 +174,7 @@ namespace ts.codefix {
|
||||
}
|
||||
}
|
||||
|
||||
function tryDeleteIdentifier(changes: textChanges.ChangeTracker, sourceFile: SourceFile, identifier: Identifier, deletedAncestors: NodeSet | undefined): void {
|
||||
function tryDeleteIdentifier(changes: textChanges.ChangeTracker, sourceFile: SourceFile, identifier: Identifier, deletedAncestors: NodeSet | undefined, checker: TypeChecker, isFixAll: boolean): void {
|
||||
const parent = identifier.parent;
|
||||
switch (parent.kind) {
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
@@ -194,11 +198,8 @@ namespace ts.codefix {
|
||||
break;
|
||||
|
||||
case SyntaxKind.Parameter:
|
||||
if (!mayDeleteParameter(parent as ParameterDeclaration, checker, isFixAll)) break;
|
||||
const oldFunction = parent.parent;
|
||||
if (isSetAccessor(oldFunction)) {
|
||||
// Setter must have a parameter
|
||||
break;
|
||||
}
|
||||
|
||||
if (isArrowFunction(oldFunction) && oldFunction.parameters.length === 1) {
|
||||
// Lambdas with exactly one parameter are special because, after removal, there
|
||||
@@ -347,4 +348,35 @@ namespace ts.codefix {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mayDeleteParameter(p: ParameterDeclaration, checker: TypeChecker, isFixAll: boolean) {
|
||||
const parent = p.parent;
|
||||
switch (parent.kind) {
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
// Don't remove a parameter if this overrides something
|
||||
const symbol = checker.getSymbolAtLocation(parent.name)!;
|
||||
if (isMemberSymbolInBaseType(symbol, checker)) return false;
|
||||
// falls through
|
||||
|
||||
case SyntaxKind.Constructor:
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.ArrowFunction: {
|
||||
// Can't remove a non-last parameter. Can remove a parameter in code-fix-all if future parameters are also unused.
|
||||
const { parameters } = parent;
|
||||
const index = parameters.indexOf(p);
|
||||
Debug.assert(index !== -1);
|
||||
return isFixAll
|
||||
? parameters.slice(index + 1).every(p => p.name.kind === SyntaxKind.Identifier && !p.symbol.isReferenced)
|
||||
: index === parameters.length - 1;
|
||||
}
|
||||
|
||||
case SyntaxKind.SetAccessor:
|
||||
// Setter must have a parameter
|
||||
return false;
|
||||
|
||||
default:
|
||||
return Debug.failBadSyntaxKind(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,15 +16,18 @@ namespace ts.moduleSpecifiers {
|
||||
const getCanonicalFileName = hostGetCanonicalFileName(host);
|
||||
const sourceDirectory = getDirectoryPath(importingSourceFile.fileName);
|
||||
|
||||
return getAllModulePaths(program, moduleSymbol.valueDeclaration.getSourceFile()).map(moduleFileName => {
|
||||
const global = tryGetModuleNameFromAmbientModule(moduleSymbol)
|
||||
|| tryGetModuleNameFromTypeRoots(compilerOptions, host, getCanonicalFileName, moduleFileName, addJsExtension)
|
||||
|| tryGetModuleNameAsNodeModule(compilerOptions, moduleFileName, host, getCanonicalFileName, sourceDirectory)
|
||||
|| rootDirs && tryGetModuleNameFromRootDirs(rootDirs, moduleFileName, sourceDirectory, getCanonicalFileName);
|
||||
if (global) {
|
||||
return [global];
|
||||
}
|
||||
const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol);
|
||||
if (ambient) return [[ambient]];
|
||||
|
||||
const modulePaths = getAllModulePaths(program, moduleSymbol.valueDeclaration.getSourceFile());
|
||||
|
||||
const global = mapDefined(modulePaths, moduleFileName =>
|
||||
tryGetModuleNameFromTypeRoots(compilerOptions, host, getCanonicalFileName, moduleFileName, addJsExtension) ||
|
||||
tryGetModuleNameAsNodeModule(compilerOptions, moduleFileName, host, getCanonicalFileName, sourceDirectory) ||
|
||||
rootDirs && tryGetModuleNameFromRootDirs(rootDirs, moduleFileName, sourceDirectory, getCanonicalFileName));
|
||||
if (global.length) return global.map(g => [g]);
|
||||
|
||||
return modulePaths.map(moduleFileName => {
|
||||
const relativePath = removeExtensionAndIndexPostFix(ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, moduleFileName, getCanonicalFileName)), moduleResolutionKind, addJsExtension);
|
||||
if (!baseUrl || preferences.importModuleSpecifierPreference === "relative") {
|
||||
return [relativePath];
|
||||
@@ -191,11 +194,12 @@ namespace ts.moduleSpecifiers {
|
||||
// Simplify the full file path to something that can be resolved by Node.
|
||||
|
||||
// If the module could be imported by a directory name, use that directory's name
|
||||
let moduleSpecifier = getDirectoryOrExtensionlessFileName(moduleFileName);
|
||||
const moduleSpecifier = getDirectoryOrExtensionlessFileName(moduleFileName);
|
||||
// Get a path that's relative to node_modules or the importing file's path
|
||||
moduleSpecifier = getNodeResolvablePath(moduleSpecifier);
|
||||
// if node_modules folder is in this folder or any of its parent folders, no need to keep it.
|
||||
if (!startsWith(sourceDirectory, moduleSpecifier.substring(0, parts.topLevelNodeModulesIndex))) return undefined;
|
||||
// If the module was found in @types, get the actual Node package name
|
||||
return getPackageNameFromAtTypesDirectory(moduleSpecifier);
|
||||
return getPackageNameFromAtTypesDirectory(moduleSpecifier.substring(parts.topLevelPackageNameIndex + 1));
|
||||
|
||||
function getDirectoryOrExtensionlessFileName(path: string): string {
|
||||
// If the file is the main module, it can be imported by the package name
|
||||
@@ -224,17 +228,6 @@ namespace ts.moduleSpecifiers {
|
||||
|
||||
return fullModulePathWithoutExtension;
|
||||
}
|
||||
|
||||
function getNodeResolvablePath(path: string): string {
|
||||
const basePath = path.substring(0, parts.topLevelNodeModulesIndex);
|
||||
if (sourceDirectory.indexOf(basePath) === 0) {
|
||||
// if node_modules folder is in this folder or any of its parent folders, no need to keep it.
|
||||
return path.substring(parts.topLevelPackageNameIndex + 1);
|
||||
}
|
||||
else {
|
||||
return ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, path, getCanonicalFileName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface NodeModulePathParts {
|
||||
|
||||
@@ -1320,12 +1320,20 @@ namespace ts.Completions {
|
||||
function getSymbolsFromOtherSourceFileExports(symbols: Symbol[], tokenText: string, target: ScriptTarget): void {
|
||||
const tokenTextLowerCase = tokenText.toLowerCase();
|
||||
|
||||
const seenResolvedModules = createMap<true>();
|
||||
|
||||
codefix.forEachExternalModuleToImportFrom(typeChecker, sourceFile, program.getSourceFiles(), moduleSymbol => {
|
||||
// Perf -- ignore other modules if this is a request for details
|
||||
if (detailsEntryId && detailsEntryId.source && stripQuotes(moduleSymbol.name) !== detailsEntryId.source) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resolvedModuleSymbol = typeChecker.resolveExternalModuleSymbol(moduleSymbol);
|
||||
// resolvedModuleSymbol may be a namespace. A namespace may be `export =` by multiple module declarations, but only keep the first one.
|
||||
if (!addToSeen(seenResolvedModules, getSymbolId(resolvedModuleSymbol))) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let symbol of typeChecker.getExportsOfModule(moduleSymbol)) {
|
||||
// Don't add a completion for a re-export, only for the original.
|
||||
// The actual import fix might end up coming from a re-export -- we don't compute that until getting completion details.
|
||||
@@ -1333,7 +1341,7 @@ namespace ts.Completions {
|
||||
//
|
||||
// If `symbol.parent !== ...`, this comes from an `export * from "foo"` re-export. Those don't create new symbols.
|
||||
// If `some(...)`, this comes from an `export { foo } from "foo"` re-export, which creates a new symbol (thus isn't caught by the first check).
|
||||
if (typeChecker.getMergedSymbol(symbol.parent!) !== typeChecker.resolveExternalModuleSymbol(moduleSymbol)
|
||||
if (typeChecker.getMergedSymbol(symbol.parent!) !== resolvedModuleSymbol
|
||||
|| some(symbol.declarations, d => isExportSpecifier(d) && !!d.parent.parent.moduleSpecifier)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ namespace ts.DocumentHighlights {
|
||||
default:
|
||||
// Don't cross function boundaries.
|
||||
// TODO: GH#20090
|
||||
return (isFunctionLike(node) && "quit") as false | "quit";
|
||||
return isFunctionLike(node) && "quit";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1399,34 +1399,6 @@ namespace ts.FindAllReferences.Core {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find symbol of the given property-name and add the symbol to the given result array
|
||||
* @param symbol a symbol to start searching for the given propertyName
|
||||
* @param propertyName a name of property to search for
|
||||
* @param result an array of symbol of found property symbols
|
||||
* @param previousIterationSymbolsCache a cache of symbol from previous iterations of calling this function to prevent infinite revisiting of the same symbol.
|
||||
* The value of previousIterationSymbol is undefined when the function is first called.
|
||||
*/
|
||||
function getPropertySymbolsFromBaseTypes<T>(symbol: Symbol, propertyName: string, checker: TypeChecker, cb: (symbol: Symbol) => T | undefined): T | undefined {
|
||||
const seen = createMap<true>();
|
||||
return recur(symbol);
|
||||
|
||||
function recur(symbol: Symbol): T | undefined {
|
||||
// Use `addToSeen` to ensure we don't infinitely recurse in this situation:
|
||||
// interface C extends C {
|
||||
// /*findRef*/propName: string;
|
||||
// }
|
||||
if (!(symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) || !addToSeen(seen, getSymbolId(symbol))) return;
|
||||
|
||||
return firstDefined(symbol.declarations, declaration => firstDefined(getAllSuperTypeNodes(declaration), typeReference => {
|
||||
const type = checker.getTypeAtLocation(typeReference);
|
||||
const propertySymbol = type && type.symbol && checker.getPropertyOfType(type, propertyName);
|
||||
// Visit the typeReference as well to see if it directly or indirectly uses that property
|
||||
return propertySymbol && (firstDefined(checker.getRootSymbols(propertySymbol), cb) || recur(type!.symbol));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function getRelatedSymbol(search: Search, referenceSymbol: Symbol, referenceLocation: Node, state: State): Symbol | undefined {
|
||||
const { checker } = state;
|
||||
return forEachRelatedSymbol(referenceSymbol, referenceLocation, checker,
|
||||
|
||||
+11
-16
@@ -243,7 +243,7 @@ namespace ts.JsDoc {
|
||||
}
|
||||
|
||||
const tokenAtPos = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false);
|
||||
const tokenStart = tokenAtPos.getStart();
|
||||
const tokenStart = tokenAtPos.getStart(sourceFile);
|
||||
if (!tokenAtPos || tokenStart < position) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -253,7 +253,7 @@ namespace ts.JsDoc {
|
||||
return undefined;
|
||||
}
|
||||
const { commentOwner, parameters } = commentOwnerInfo;
|
||||
if (commentOwner.getStart() < position) {
|
||||
if (commentOwner.getStart(sourceFile) < position) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -268,19 +268,6 @@ namespace ts.JsDoc {
|
||||
|
||||
// replace non-whitespace characters in prefix with spaces.
|
||||
const indentationStr = sourceFile.text.substr(lineStart, posLineAndChar.character).replace(/\S/i, () => " ");
|
||||
const isJavaScriptFile = hasJavaScriptFileExtension(sourceFile.fileName);
|
||||
|
||||
let docParams = "";
|
||||
for (let i = 0; i < parameters.length; i++) {
|
||||
const currentName = parameters[i].name;
|
||||
const paramName = currentName.kind === SyntaxKind.Identifier ? currentName.escapedText : "param" + i;
|
||||
if (isJavaScriptFile) {
|
||||
docParams += `${indentationStr} * @param {any} ${paramName}${newLine}`;
|
||||
}
|
||||
else {
|
||||
docParams += `${indentationStr} * @param ${paramName}${newLine}`;
|
||||
}
|
||||
}
|
||||
|
||||
// A doc comment consists of the following
|
||||
// * The opening comment line
|
||||
@@ -293,13 +280,21 @@ namespace ts.JsDoc {
|
||||
indentationStr + " * ";
|
||||
const result =
|
||||
preamble + newLine +
|
||||
docParams +
|
||||
parameterDocComments(parameters, hasJavaScriptFileExtension(sourceFile.fileName), indentationStr, newLine) +
|
||||
indentationStr + " */" +
|
||||
(tokenStart === position ? newLine + indentationStr : "");
|
||||
|
||||
return { newText: result, caretOffset: preamble.length };
|
||||
}
|
||||
|
||||
function parameterDocComments(parameters: ReadonlyArray<ParameterDeclaration>, isJavaScriptFile: boolean, indentationStr: string, newLine: string): string {
|
||||
return parameters.map(({ name, dotDotDotToken }, i) => {
|
||||
const paramName = name.kind === SyntaxKind.Identifier ? name.text : "param" + i;
|
||||
const type = isJavaScriptFile ? (dotDotDotToken ? "{...any} " : "{any} ") : "";
|
||||
return `${indentationStr} * @param ${type}${paramName}${newLine}`;
|
||||
}).join("");
|
||||
}
|
||||
|
||||
interface CommentOwnerInfo {
|
||||
readonly commentOwner: Node;
|
||||
readonly parameters?: ReadonlyArray<ParameterDeclaration>;
|
||||
|
||||
@@ -1735,17 +1735,9 @@ namespace ts {
|
||||
synchronizeHostData();
|
||||
|
||||
// Exclude default library when renaming as commonly user don't want to change that file.
|
||||
let sourceFiles: SourceFile[] = [];
|
||||
if (options && options.isForRename) {
|
||||
for (const sourceFile of program.getSourceFiles()) {
|
||||
if (!program.isSourceFileDefaultLibrary(sourceFile)) {
|
||||
sourceFiles.push(sourceFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
sourceFiles = program.getSourceFiles().slice();
|
||||
}
|
||||
const sourceFiles = options && options.isForRename
|
||||
? program.getSourceFiles().filter(sourceFile => !program.isSourceFileDefaultLibrary(sourceFile))
|
||||
: program.getSourceFiles();
|
||||
|
||||
return FindAllReferences.findReferencedEntries(program, cancellationToken, sourceFiles, getValidSourceFile(fileName), position, options);
|
||||
}
|
||||
|
||||
@@ -1293,6 +1293,38 @@ namespace ts {
|
||||
return propSymbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find symbol of the given property-name and add the symbol to the given result array
|
||||
* @param symbol a symbol to start searching for the given propertyName
|
||||
* @param propertyName a name of property to search for
|
||||
* @param result an array of symbol of found property symbols
|
||||
* @param previousIterationSymbolsCache a cache of symbol from previous iterations of calling this function to prevent infinite revisiting of the same symbol.
|
||||
* The value of previousIterationSymbol is undefined when the function is first called.
|
||||
*/
|
||||
export function getPropertySymbolsFromBaseTypes<T>(symbol: Symbol, propertyName: string, checker: TypeChecker, cb: (symbol: Symbol) => T | undefined): T | undefined {
|
||||
const seen = createMap<true>();
|
||||
return recur(symbol);
|
||||
|
||||
function recur(symbol: Symbol): T | undefined {
|
||||
// Use `addToSeen` to ensure we don't infinitely recurse in this situation:
|
||||
// interface C extends C {
|
||||
// /*findRef*/propName: string;
|
||||
// }
|
||||
if (!(symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) || !addToSeen(seen, getSymbolId(symbol))) return;
|
||||
|
||||
return firstDefined(symbol.declarations, declaration => firstDefined(getAllSuperTypeNodes(declaration), typeReference => {
|
||||
const type = checker.getTypeAtLocation(typeReference);
|
||||
const propertySymbol = type && type.symbol && checker.getPropertyOfType(type, propertyName);
|
||||
// Visit the typeReference as well to see if it directly or indirectly uses that property
|
||||
return type && propertySymbol && (firstDefined(checker.getRootSymbols(propertySymbol), cb) || recur(type.symbol));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export function isMemberSymbolInBaseType(memberSymbol: Symbol, checker: TypeChecker): boolean {
|
||||
return getPropertySymbolsFromBaseTypes(memberSymbol.parent!, memberSymbol.name, checker, _ => true) || false;
|
||||
}
|
||||
|
||||
export class NodeSet {
|
||||
private map = createMap<Node>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user