findAllReferences: Share code between populateSearchSymbolSet and getRelatedSymbol (#23028)

This commit is contained in:
Andy
2018-04-05 15:00:14 -07:00
committed by GitHub
parent 24842b4002
commit aa8631de98
18 changed files with 111 additions and 261 deletions
+67 -122
View File
@@ -1373,26 +1373,37 @@ namespace ts.FindAllReferences.Core {
// For certain symbol kinds, we need to include other symbols in the search set.
// This is not needed when searching for re-exports.
function populateSearchSymbolSet(symbol: Symbol, location: Node, checker: TypeChecker, implementations: boolean): Symbol[] {
// The search set contains at least the current symbol
const result: Symbol[] = [];
forEachRelatedSymbol<void>(symbol, location, checker,
(sym, root, base) => { result.push(base || root || sym); },
parameterProperties => { result.push(...parameterProperties); },
/*allowBaseTypes*/ () => !implementations,
/*includeShorthandDestructuring*/ false);
return result;
}
function forEachRelatedSymbol<T>(
symbol: Symbol, location: Node, checker: TypeChecker,
cbSymbol: (symbol: Symbol, rootSymbol?: Symbol, baseSymbol?: Symbol) => T | undefined,
cbParameterProperties: (s: Symbol[]) => T | undefined,
allowBaseTypes: (rootSymbol: Symbol) => boolean,
includeShorthandDestructuring: boolean,
): T | undefined {
const containingObjectLiteralElement = getContainingObjectLiteralElement(location);
if (containingObjectLiteralElement) {
// If the location is name of property symbol from object literal destructuring pattern
// Search the property symbol
// for ( { property: p2 } of elems) { }
if (containingObjectLiteralElement.kind !== SyntaxKind.ShorthandPropertyAssignment) {
const propertySymbol = getPropertySymbolOfDestructuringAssignment(location, checker);
if (propertySymbol) {
result.push(propertySymbol);
}
}
// If the location is in a context sensitive location (i.e. in an object literal) try
// to get a contextual type for it, and add the property symbol from the contextual
// type to the search set
for (const contextualSymbol of getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker)) {
addRootSymbols(contextualSymbol);
const res = firstDefined(getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker), fromRoot);
if (res) return res;
// If the location is name of property symbol from object literal destructuring pattern
// Search the property symbol
// for ( { property: p2 } of elems) { }
if (includeShorthandDestructuring || containingObjectLiteralElement.kind !== SyntaxKind.ShorthandPropertyAssignment) {
const propertySymbol = getPropertySymbolOfDestructuringAssignment(location, checker);
const res = propertySymbol && cbSymbol(propertySymbol);
if (res) return res;
}
/* Because in short-hand property assignment, location has two meaning : property name and as value of the property
@@ -1407,49 +1418,44 @@ namespace ts.FindAllReferences.Core {
* will be included correctly.
*/
const shorthandValueSymbol = checker.getShorthandAssignmentValueSymbol(location.parent);
if (shorthandValueSymbol) {
result.push(shorthandValueSymbol);
}
const res1 = shorthandValueSymbol && cbSymbol(shorthandValueSymbol);
if (res1) return res1;
}
const res = fromRoot(symbol);
if (res) return res;
// If the symbol.valueDeclaration is a property parameter declaration,
// we should include both parameter declaration symbol and property declaration symbol
// Parameter Declaration symbol is only visible within function scope, so the symbol is stored in constructor.locals.
// Property Declaration symbol is a member of the class, so the symbol is stored in its class Declaration.symbol.members
addRange(result, getParameterPropertySymbols(symbol, checker));
if (symbol.valueDeclaration && isParameter(symbol.valueDeclaration) && isParameterPropertyDeclaration(symbol.valueDeclaration)) {
const paramProps = checker.getSymbolsOfParameterPropertyDeclaration(symbol.valueDeclaration, symbol.name);
const res = paramProps && cbParameterProperties(paramProps);
if (res) return res;
}
// If this is symbol of binding element without propertyName declaration in Object binding pattern
// Include the property in the search
const bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, checker);
if (bindingElementPropertySymbol) {
result.push(bindingElementPropertySymbol);
addRootSymbols(bindingElementPropertySymbol);
}
addRootSymbols(symbol);
return result;
function addRootSymbols(sym: Symbol): void {
// If this is a union property, add all the symbols from all its source symbols in all unioned types.
// If the symbol is an instantiation from a another symbol (e.g. widened symbol) , add the root the list
for (const rootSymbol of checker.getRootSymbols(sym)) {
result.push(rootSymbol);
return bindingElementPropertySymbol && fromRoot(bindingElementPropertySymbol);
function fromRoot(sym: Symbol): T | undefined {
// If this is a union property:
// - In populateSearchSymbolsSet we will add all the symbols from all its source symbols in all unioned types.
// - In findRelatedSymbol, we will just use the union symbol if any source symbol is included in the search.
// If the symbol is an instantiation from a another symbol (e.g. widened symbol):
// - In populateSearchSymbolsSet, add the root the list
// - In findRelatedSymbol, return the source symbol if that is in the search. (Do not return the instantiation symbol.)
return firstDefined(checker.getRootSymbols(sym), rootSymbol =>
cbSymbol(sym, rootSymbol)
// Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions
if (!implementations && rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.name, checker, result);
}
}
|| (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface) && allowBaseTypes(rootSymbol)
? getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.name, checker, base => cbSymbol(sym, rootSymbol, base))
: undefined));
}
}
function getParameterPropertySymbols(symbol: Symbol, checker: TypeChecker): Symbol[] {
return symbol.valueDeclaration && isParameter(symbol.valueDeclaration) && isParameterPropertyDeclaration(symbol.valueDeclaration)
? checker.getSymbolsOfParameterPropertyDeclaration(symbol.valueDeclaration, symbol.name)
: undefined;
}
/**
* 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
@@ -1458,100 +1464,39 @@ namespace ts.FindAllReferences.Core {
* @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(symbol: Symbol, propertyName: string, checker: TypeChecker, result: Symbol[] = []): Symbol[] {
function getPropertySymbolsFromBaseTypes<T>(symbol: Symbol, propertyName: string, checker: TypeChecker, cb: (symbol: Symbol) => T | undefined): T | undefined {
const seen = createMap<true>();
recur(symbol);
return result;
return recur(symbol);
function recur(symbol: Symbol): void {
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;
for (const declaration of symbol.declarations) {
for (const typeReference of getAllSuperTypeNodes(declaration)) {
const type = checker.getTypeAtLocation(typeReference);
if (!(type && type.symbol)) continue;
const propertySymbol = checker.getPropertyOfType(type, propertyName);
if (propertySymbol) {
result.push(...checker.getRootSymbols(propertySymbol));
}
// Visit the typeReference as well to see if it directly or indirectly use that property
recur(type.symbol);
}
}
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;
if (search.includes(referenceSymbol)) {
return referenceSymbol;
}
if (referenceSymbol.flags & SymbolFlags.FunctionScopedVariable) {
Debug.assert(!(referenceSymbol.flags & SymbolFlags.Property));
const paramProps = getParameterPropertySymbols(referenceSymbol, checker);
if (paramProps) {
return getRelatedSymbol(search, find(paramProps, x => !!(x.flags & SymbolFlags.Property))!, referenceLocation, state);
}
}
// If the reference location is in an object literal, try to get the contextual type for the
// object literal, lookup the property symbol in the contextual type, and use this symbol to
// compare to our searchSymbol
const containingObjectLiteralElement = getContainingObjectLiteralElement(referenceLocation);
if (containingObjectLiteralElement) {
const contextualSymbol = firstDefined(getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker), findRootSymbol);
if (contextualSymbol) {
return contextualSymbol;
}
// If the reference location is the name of property from object literal destructuring pattern
// Get the property symbol from the object literal's type and look if thats the search symbol
// In below eg. get 'property' from type of elems iterating type
// for ( { property: p2 } of elems) { }
const propertySymbol = getPropertySymbolOfDestructuringAssignment(referenceLocation, checker);
if (propertySymbol && search.includes(propertySymbol)) {
return propertySymbol;
}
}
// If the reference location is the binding element and doesn't have property name
// then include the binding element in the related symbols
// let { a } : { a };
const bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(referenceSymbol, checker);
if (bindingElementPropertySymbol) {
const fromBindingElement = findRootSymbol(bindingElementPropertySymbol);
if (fromBindingElement) return fromBindingElement;
}
return findRootSymbol(referenceSymbol);
function findRootSymbol(sym: Symbol): Symbol | undefined {
// Unwrap symbols to get to the root (e.g. transient symbols as a result of widening)
// Or a union property, use its underlying unioned symbols
return firstDefined(checker.getRootSymbols(sym), rootSymbol =>
isMatchingRootSymbol(search, rootSymbol, state.inheritsFromCache, checker)
// For a root symbol that is a component of a union or intersection, use the original (union/intersection) symbol.
// That we when a symbol references the whole union we avoid claiming it references some particular member of the union.
// For a transient symbol we want to use the root symbol instead.
? getCheckFlags(sym) & CheckFlags.Synthetic ? sym : rootSymbol
: undefined);
}
}
function isMatchingRootSymbol(search: Search, rootSymbol: Symbol, inheritsFromCache: Map<boolean>, checker: TypeChecker): boolean {
return search.includes(rootSymbol)
|| rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)
// If we were passed a parent symbol (if 'implementations' set), only include types that are subtypes of the parent symbol.
&& !(search.parents && !search.parents.some(parent => explicitlyInheritsFrom(rootSymbol.parent, parent, inheritsFromCache, checker)))
// Try all properties with the same name in any type the containing type extended or implemented.
&& getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.name, checker).some(search.includes);
return forEachRelatedSymbol(referenceSymbol, referenceLocation, checker,
(sym, rootSymbol, baseSymbol) => search.includes(baseSymbol || rootSymbol || sym)
// For a base type, use the symbol for the derived type. For a synthetic (e.g. union) property, use the union symbol.
? rootSymbol && !(getCheckFlags(sym) & CheckFlags.Synthetic) ? rootSymbol : sym
: undefined,
paramProps => referenceSymbol.flags & SymbolFlags.FunctionScopedVariable
? getRelatedSymbol(search, find(paramProps, x => !!(x.flags & SymbolFlags.Property))!, referenceLocation, state)
: undefined,
/*allowBaseTypes*/ rootSymbol =>
!(search.parents && !some(search.parents, parent => explicitlyInheritsFrom(rootSymbol.parent, parent, state.inheritsFromCache, checker))),
/*includeShorthandDestructuring*/ true);
}
/** Gets all symbols for one property. Does not get symbols for every property. */
@@ -12,13 +12,9 @@
//// ["[|{| "isDefinition": true |}prop1|]"]: function () { },
////}
const [r0, r1, r2] = test.ranges();
verify.referenceGroups([r0, r1], [
const ranges = test.ranges();
const [r0, r1, r2] = ranges;
verify.referenceGroups(ranges, [
{ definition: { text: '(property) I["prop1"]: () => void', range: r0 }, ranges: [r0, r2] },
{ definition: { text: '(property) C["prop1"]: any', range: r1 }, ranges: [r1] },
]);
verify.referenceGroups(r2, [
{ definition: { text: '(property) I["prop1"]: () => void', range: r0 }, ranges: [r0] },
{ definition: { text: '(property) C["prop1"]: any', range: r1 }, ranges: [r1] },
{ definition: { text: '(property) ["prop1"]: () => void', range: r2 }, ranges: [r2] },
]);
@@ -12,13 +12,9 @@
//// ["[|{| "isDefinition": true |}42|]"]: function () { }
////}
const [r0, r1, r2] = test.ranges();
verify.referenceGroups([r0, r1], [
const ranges = test.ranges();
const [r0, r1, r2] = ranges;
verify.referenceGroups(ranges, [
{ definition: { text: '(method) I[42](): void', range: r0 }, ranges: [r0, r2] },
{ definition: { text: '(property) C[42]: any', range: r1 }, ranges: [r1] },
]);
verify.referenceGroups(r2, [
{ definition: { text: '(method) I[42](): void', range: r0 }, ranges: [r0] },
{ definition: { text: '(property) C[42]: any', range: r1 }, ranges: [r1] },
{ definition: { text: '(property) ["42"]: () => void', range: r2 }, ranges: [r2] },
]);
@@ -6,14 +6,4 @@
////const u: U = { [|{| "isWriteAccess": true, "isDefinition": true |}a|]: "" }
////const v: V = { [|{| "isWriteAccess": true, "isDefinition": true |}a|]: true }
const ranges = test.ranges();
const [r0, r1, r2] = ranges;
verify.referenceGroups(r0, [{ definition: "(property) T.a: number", ranges }]);
verify.referenceGroups(r1, [
{ definition: "(property) T.a: number", ranges: [r0, r2] },
{ definition: "(property) a: string", ranges: [r1] }
]);
verify.referenceGroups(r2, [
{ definition: "(property) T.a: number", ranges: [r0, r1] },
{ definition: "(property) a: true", ranges: [r2] }
]);
verify.singleReferenceGroup("(property) T.a: number");
@@ -18,12 +18,8 @@
const ranges = test.ranges();
const [r0, r1, r2, r3, r4] = ranges;
verify.referenceGroups([r0, r1, r3], [{ definition: "(property) I.property1: number", ranges }]);
verify.referenceGroups([r0, r1, r3, r4], [{ definition: "(property) I.property1: number", ranges }]);
verify.referenceGroups(r2, [
{ definition: "(property) I.property1: number", ranges: [r0, r1, r3, r4] },
{ definition: "let property1: number", ranges: [r2] }
]);
verify.referenceGroups(r4, [
{ definition: "(property) I.property1: number", ranges: [r0, r1, r2, r3] },
{ definition: "(property) property1: any", ranges: [r4] }
]);
@@ -4,10 +4,4 @@
////
////p, [{ [|{| "isWriteAccess": true, "isDefinition": true |}a|]: p, b }] = [{ [|{| "isWriteAccess": true, "isDefinition": true |}a|]: 10, b: true }];
const ranges = test.ranges();
const [r0, r1] = ranges;
verify.referenceGroups(r0, [{ definition: "(property) a: any", ranges }]);
verify.referenceGroups(r1, [
{ definition: "(property) a: any", ranges: [r0] },
{ definition: "(property) a: number", ranges: [r1] }
]);
verify.singleReferenceGroup("(property) a: any");
@@ -17,14 +17,4 @@
//// [|{| "isWriteAccess": true, "isDefinition": true |}a|]: "ss"
////};
const ranges = test.ranges();
const [r0, r1, r2, r3] = ranges;
verify.referenceGroups([r0, r2], [{ definition: "(property) IFoo.a: string", ranges }]);
verify.referenceGroups(r1, [
{ definition: "(property) IFoo.a: string", ranges: [r0, r2, r3] },
{ definition: "(property) a: string", ranges: [r1] }
]);
verify.referenceGroups(r3, [
{ definition: "(property) IFoo.a: string", ranges: [r0, r1, r2] },
{ definition: "(property) a: string", ranges: [r3] }
]);
verify.singleReferenceGroup("(property) IFoo.a: string");
@@ -11,18 +11,9 @@
////const a: A = { [|{| "isWriteAccess": true, "isDefinition": true |}x|]: 0 };
////const b: B = { [|{| "isWriteAccess": true, "isDefinition": true |}x|]: 0 };
const [r0, r1, r2, r3] = test.ranges();
verify.referenceGroups([r0, r1], [
const ranges = test.ranges();
const [r0, r1, r2, r3] = ranges;
verify.referenceGroups(ranges, [
{ definition: "(property) A.x: string | number", ranges: [r0, r2] },
{ definition: "(property) B.x: number", ranges: [r1, r3] },
]);
verify.referenceGroups(r2, [
{ definition: "(property) A.x: string | number", ranges: [r0] },
{ definition: "(property) B.x: number", ranges: [r1, r3] },
{ definition: "(property) x: number", ranges: [r2] },
]);
verify.referenceGroups(r3, [
{ definition: "(property) A.x: string | number", ranges: [r0, r2] },
{ definition: "(property) B.x: number", ranges: [r1] },
{ definition: "(property) x: number", ranges: [r3] },
]);
@@ -7,10 +7,10 @@
//// obj.[|name|];
const [r0, r1, r2, r3, r4] = test.ranges();
verify.referenceGroups(r0, [{ definition: "var name: string", ranges: [r0, r1, r3] }]); //r3
verify.referenceGroups([r0, r3], [{ definition: "var name: string", ranges: [r0, r1, r3] }]);
verify.referenceGroups(r1, [
{ definition: "var name: string", ranges: [r0, r3] },
{ definition: "(property) name: string", ranges: [r1, r4] }
{ definition: "var name: string", ranges: [r0, r1, r3] },
{ definition: "(property) name: string", ranges: [r4] }
]);
verify.singleReferenceGroup("(property) name: string", [r2]);
verify.referenceGroups(r4, [{ definition: "(property) name: string", ranges: [r1, r4] }]);
@@ -13,7 +13,7 @@ const [r0, r1, r2, r3] = test.ranges();
verify.singleReferenceGroup("var dx: string", [r0]);
verify.referenceGroups(r1, [{ definition: "var M.dx: any", ranges: [r1, r2] }]);
verify.referenceGroups(r2, [
{ definition: "var M.dx: any", ranges: [r1] },
{ definition: "(property) dx: any", ranges: [r2, r3] }
{ definition: "var M.dx: any", ranges: [r1, r2] },
{ definition: "(property) dx: any", ranges: [r3] }
]);
verify.referenceGroups(r3, [{ definition: "(property) dx: any", ranges: [r2, r3] }]);
@@ -14,11 +14,4 @@
// @Filename: redeclaration.ts
////container = { "[|{| "isWriteAccess": true, "isDefinition": true |}searchProp|]" : 18 };
const ranges = test.ranges();
const [r0, r1, r2, r3] = ranges;
verify.referenceGroups(r0, [{ definition: "(property) searchProp: number", ranges }]);
verify.referenceGroups([r1, r2], [{ definition: "(property) searchProp: number", ranges }]);
verify.referenceGroups(r3, [
{ definition: "(property) searchProp: number", ranges: [r0, r1, r2] },
{ definition: '(property) "searchProp": number', ranges: [r3] }
]);
verify.singleReferenceGroup("(property) searchProp: number");
@@ -14,10 +14,4 @@
// @Filename: redeclaration.ts
////container = { "[|{| "isWriteAccess": true, "isDefinition": true |}42|]" : 18 };
const ranges = test.ranges();
const [r0, r1, r2, r3] = ranges;
verify.referenceGroups([r0, r1, r2], [{ definition: "(property) 42: number", ranges }]);
verify.referenceGroups(r3, [
{ definition: "(property) 42: number", ranges: [r0, r1, r2] },
{ definition: '(property) "42": number', ranges: [r3] }
]);
verify.singleReferenceGroup("(property) 42: number");
+15
View File
@@ -0,0 +1,15 @@
/// <reference path='fourslash.ts'/>
//// class Foo {
//// constructor(private [|{| "isWriteAccess": true, "isDefinition": true |}privateParam|]: number) {
//// let localPrivate = [|privateParam|];
//// this.[|{| "isWriteAccess": true |}privateParam|] += 10;
//// }
//// }
const ranges = test.ranges();
const [r0, r1, r2] = ranges;
verify.referenceGroups(ranges, [
{ definition: "(property) Foo.privateParam: number", ranges: [r0, r2] },
{ definition: "(parameter) privateParam: number", ranges: [r1] }
]);
@@ -25,12 +25,4 @@
////// Untped -- should not be included
////var u = { xy: 0 };
const ranges = test.ranges();
verify.referenceGroups(ranges[0], [{ definition: "(property) IFoo.xy: number", ranges }]);
for (const range of ranges.slice(1)) {
const type = range.marker.data.type || "number";
verify.referenceGroups(range, [
{ definition: "(property) IFoo.xy: number", ranges: ranges.filter(r => r !== range) },
{ definition: `(property) xy: ${type}`, ranges: [range] }
]);
}
verify.singleReferenceGroup("(property) IFoo.xy: number");
@@ -44,21 +44,8 @@ verify.referenceGroups(bCommon, [
{ definition: "(property) B.common: number", ranges: [bCommon] },
{ definition: "(property) common: string | number", ranges: unionRefs },
]);
unionRefs.forEach((unionRef, idx) => {
const type = unionRef.marker.data.type;
const last2 = [
{ definition: `(property) common: string | number`, ranges: unionRefs.filter(u => u != unionRef) },
{ definition: `(property) common: ${type}`, ranges: [unionRef] },
];
if (idx === 0) {
const tmp = last2[0];
last2[0] = last2[1];
last2[1] = tmp;
}
verify.referenceGroups(unionRef, [
{ definition: "(property) A.common: string", ranges: [aCommon] },
{ definition: "(property) B.common: number", ranges: [bCommon] },
...last2,
]);
});
verify.referenceGroups(unionRefs, [
{ definition: "(property) A.common: string", ranges: [aCommon] },
{ definition: "(property) B.common: number", ranges: [bCommon] },
{ definition: `(property) common: string | number`, ranges: unionRefs },
]);
@@ -34,12 +34,4 @@
////var u1 = { a: 0, b: 0, common: "" };
////var u2 = { b: 0, common: 0 };
const ranges = test.ranges();
verify.referenceGroups(ranges[0], [{ definition: "(property) B.b: number", ranges }]);
for (const reference of ranges.slice(1)) {
const type = reference.marker.data.type;
verify.referenceGroups(reference, [
{ definition: "(property) B.b: number", ranges: ranges.filter(r => r !== reference) },
{ definition: `(property) b: ${type}`, ranges: [reference] }
]);
}
verify.singleReferenceGroup("(property) B.b: number");
@@ -9,15 +9,4 @@
////x = { "[|{| "isWriteAccess": true, "isDefinition": true |}12|]": 0 };
////x = { [|{| "isWriteAccess": true, "isDefinition": true |}12|]: 0 };
//verify.singleReferenceGroup("(property) Foo[12]: any");
const ranges = test.ranges();
const [r0, r1, r2, r3] = ranges;
verify.referenceGroups([r0, r1], [{ definition: "(property) Foo[12]: any", ranges }]);
verify.referenceGroups(r2, [
{ definition: "(property) Foo[12]: any", ranges: [r0, r1, r3] },
{ definition: "(property) \"12\": number", ranges: [r2] }
]);
verify.referenceGroups(r3, [
{ definition: "(property) Foo[12]: any", ranges: [r0, r1, r2] },
{ definition: "(property) 12: number", ranges: [r3] }
]);
verify.singleReferenceGroup("(property) Foo[12]: any");
@@ -10,14 +10,4 @@
////x = { "[|{| "isWriteAccess": true, "isDefinition": true |}ss|]": 0 };
////x = { [|{| "isWriteAccess": true, "isDefinition": true |}ss|]: 0 };
const ranges = test.ranges();
const [r0, r1, r2, r3, r4] = ranges;
verify.referenceGroups([r0, r1, r2], [{ definition: '(property) Foo["ss"]: any', ranges }]);
verify.referenceGroups(r3, [
{ definition: '(property) Foo["ss"]: any', ranges: [r0, r1, r2, r4] },
{ definition: '(property) "ss": number', ranges: [r3] }
]);
verify.referenceGroups(r4, [
{ definition: '(property) Foo["ss"]: any', ranges: [r0, r1, r2, r3] },
{ definition: '(property) ss: number', ranges: [r4] }
]);
verify.singleReferenceGroup('(property) Foo["ss"]: any');