144 lines
No EOL
5.4 KiB
JavaScript
144 lines
No EOL
5.4 KiB
JavaScript
|
|
//#region ../shared/src/index.ts
|
|
function* forEachTouchingNode(ts, sourceFile, position) {
|
|
yield* binaryVisit(ts, sourceFile, sourceFile, position);
|
|
}
|
|
function* binaryVisit(ts, sourceFile, node, position) {
|
|
const nodes = [];
|
|
ts.forEachChild(node, (child) => {
|
|
nodes.push(child);
|
|
});
|
|
let left = 0;
|
|
let right = nodes.length - 1;
|
|
while (left <= right) {
|
|
const mid = Math.floor((left + right) / 2);
|
|
const node$1 = nodes[mid];
|
|
if (position > node$1.getEnd()) left = mid + 1;
|
|
else if (position < node$1.getStart(sourceFile)) right = mid - 1;
|
|
else {
|
|
yield node$1;
|
|
yield* binaryVisit(ts, sourceFile, node$1, position);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
function isTextSpanWithin(node, textSpan, sourceFile) {
|
|
return textSpan.start + textSpan.length <= node.getEnd() && textSpan.start >= node.getStart(sourceFile);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/index.ts
|
|
const plugin = (module$1) => {
|
|
const { typescript: ts } = module$1;
|
|
return { create(info) {
|
|
for (const [key, method] of [
|
|
["findRenameLocations", findRenameLocations],
|
|
["findReferences", findReferences],
|
|
["getDefinitionAndBoundSpan", getDefinitionAndBoundSpan]
|
|
]) {
|
|
const original = info.languageService[key];
|
|
info.languageService[key] = method(ts, info, original);
|
|
}
|
|
return info.languageService;
|
|
} };
|
|
};
|
|
var src_default = plugin;
|
|
const declarationRE = /\.d\.(?:c|m)?ts$/;
|
|
function createVisitor(getter) {
|
|
return (ts, textSpan, sourceFile) => {
|
|
for (const node of forEachTouchingNode(ts, sourceFile, textSpan.start)) if (ts.isPropertySignature(node) && node.type || ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.type) {
|
|
const target = getter(ts, node.name, node.type, textSpan, sourceFile);
|
|
if (target) return target;
|
|
}
|
|
};
|
|
}
|
|
const visitForwardImports = createVisitor((ts, name, type, textSpan, sourceFile) => {
|
|
if (!isTextSpanWithin(name, textSpan, sourceFile)) return;
|
|
while (ts.isTypeReferenceNode(type) && type.typeArguments?.length) type = type.typeArguments[0];
|
|
if (ts.isIndexedAccessTypeNode(type)) return type.indexType;
|
|
else if (ts.isImportTypeNode(type)) return type.qualifier ?? type.argument;
|
|
});
|
|
const visitBackwardImports = createVisitor((ts, name, type, textSpan, sourceFile) => {
|
|
while (ts.isTypeReferenceNode(type) && type.typeArguments?.length) type = type.typeArguments[0];
|
|
let target;
|
|
if (ts.isIndexedAccessTypeNode(type)) target = type.indexType;
|
|
else if (ts.isImportTypeNode(type)) target = type.qualifier ?? type.argument;
|
|
else return;
|
|
if (isTextSpanWithin(target, textSpan, sourceFile)) return name;
|
|
});
|
|
function findRenameLocations(ts, info, findRenameLocations$1) {
|
|
return (...args) => {
|
|
const result = findRenameLocations$1(...args);
|
|
if (!result?.length) return result;
|
|
const program = info.languageService.getProgram();
|
|
const preferences = typeof args[4] === "object" ? args[4] : {};
|
|
const locations = [...result];
|
|
for (const location of result) {
|
|
const sourceFile = program.getSourceFile(location.fileName);
|
|
if (!sourceFile) continue;
|
|
if (!declarationRE.test(location.fileName)) continue;
|
|
const args$1 = [
|
|
ts,
|
|
location.textSpan,
|
|
sourceFile
|
|
];
|
|
const node = visitForwardImports(...args$1) ?? visitBackwardImports(...args$1);
|
|
if (!node) continue;
|
|
const position = node.getStart(sourceFile);
|
|
const res = findRenameLocations$1(location.fileName, position, false, false, preferences);
|
|
if (res?.length) locations.push(...res);
|
|
}
|
|
return locations;
|
|
};
|
|
}
|
|
function findReferences(ts, info, findReferences$1) {
|
|
return (...args) => {
|
|
const result = findReferences$1(...args);
|
|
if (!result?.length) return result;
|
|
const program = info.languageService.getProgram();
|
|
for (const symbol of result) {
|
|
const references = new Set(symbol.references);
|
|
for (const reference of symbol.references) {
|
|
const sourceFile = program.getSourceFile(reference.fileName);
|
|
if (!sourceFile) continue;
|
|
if (!declarationRE.test(reference.fileName)) continue;
|
|
const node = visitBackwardImports(ts, reference.textSpan, sourceFile);
|
|
if (!node) continue;
|
|
const position = node.getStart(sourceFile) + 1;
|
|
const res = info.languageService.getReferencesAtPosition(reference.fileName, position)?.filter((entry) => entry.fileName !== reference.fileName || position < entry.textSpan.start || position > entry.textSpan.start + entry.textSpan.length);
|
|
references.delete(reference);
|
|
for (const reference$1 of res ?? []) references.add(reference$1);
|
|
}
|
|
symbol.references = [...references];
|
|
}
|
|
return result;
|
|
};
|
|
}
|
|
function getDefinitionAndBoundSpan(ts, info, getDefinitionAndBoundSpan$1) {
|
|
return (...args) => {
|
|
const result = getDefinitionAndBoundSpan$1(...args);
|
|
if (!result?.definitions?.length) return result;
|
|
const program = info.languageService.getProgram();
|
|
const definitions = new Set(result.definitions);
|
|
for (const definition of result.definitions) {
|
|
const sourceFile = program.getSourceFile(definition.fileName);
|
|
if (!sourceFile) continue;
|
|
if (!declarationRE.test(definition.fileName)) continue;
|
|
const node = visitForwardImports(ts, definition.textSpan, sourceFile);
|
|
if (!node) continue;
|
|
const position = node.getStart(sourceFile);
|
|
const res = getDefinitionAndBoundSpan$1(definition.fileName, position);
|
|
if (res?.definitions?.length) {
|
|
definitions.delete(definition);
|
|
for (const definition$1 of res.definitions) definitions.add(definition$1);
|
|
}
|
|
}
|
|
return {
|
|
definitions: [...definitions],
|
|
textSpan: result.textSpan
|
|
};
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
module.exports = src_default; |