svelte-language-server
Version:
A language server for Svelte
325 lines • 15.4 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CallHierarchyProviderImpl = void 0;
const path_1 = __importStar(require("path"));
const typescript_1 = __importDefault(require("typescript"));
const vscode_languageserver_1 = require("vscode-languageserver");
const vscode_languageserver_types_1 = require("vscode-languageserver-types");
const documents_1 = require("../../../lib/documents");
const utils_1 = require("../../../utils");
const DocumentSnapshot_1 = require("../DocumentSnapshot");
const utils_2 = require("../utils");
const utils_3 = require("./utils");
const svelte2tsx_1 = require("svelte2tsx");
const ENSURE_COMPONENT_HELPER = '__sveltets_2_ensureComponent';
class CallHierarchyProviderImpl {
constructor(lsAndTsDocResolver, workspaceUris) {
this.lsAndTsDocResolver = lsAndTsDocResolver;
this.workspaceUris = workspaceUris;
}
async prepareCallHierarchy(document, position, cancellationToken) {
const { lang, tsDoc, lsContainer } = await this.lsAndTsDocResolver.getLSAndTSDoc(document);
if (cancellationToken?.isCancellationRequested) {
return null;
}
const offset = tsDoc.offsetAt(tsDoc.getGeneratedPosition(position));
const items = lang.prepareCallHierarchy(tsDoc.filePath, offset);
const itemsArray = Array.isArray(items) ? items : items ? [items] : [];
const snapshots = new utils_3.SnapshotMap(this.lsAndTsDocResolver, lsContainer);
snapshots.set(tsDoc.filePath, tsDoc);
const program = lang.getProgram();
const result = await Promise.all(itemsArray.map((item) => this.convertCallHierarchyItem(snapshots, item, program)));
return result.filter(utils_1.isNotNullOrUndefined);
}
isSourceFileItem(item) {
return (item.kind === typescript_1.default.ScriptElementKind.scriptElement ||
(item.kind === typescript_1.default.ScriptElementKind.moduleElement && item.selectionSpan.start === 0));
}
async convertCallHierarchyItem(snapshots, item, program) {
const snapshot = await snapshots.retrieve(item.file);
const redirectedCallHierarchyItem = this.redirectCallHierarchyItem(snapshot, program, item);
if (redirectedCallHierarchyItem) {
return redirectedCallHierarchyItem;
}
const { name, detail } = this.getNameAndDetailForItem(this.isSourceFileItem(item), item);
const selectionRange = (0, documents_1.mapRangeToOriginal)(snapshot, (0, utils_2.convertRange)(snapshot, item.selectionSpan));
if (selectionRange.start.line < 0 || selectionRange.end.line < 0) {
return null;
}
const range = (0, documents_1.mapRangeToOriginal)(snapshot, (0, utils_2.convertRange)(snapshot, item.span));
if (range.start.line < 0 || range.end.line < 0) {
return null;
}
return {
kind: (0, utils_2.symbolKindFromString)(item.kind),
name,
range,
selectionRange,
uri: (0, utils_1.pathToUrl)(item.file),
detail,
tags: item.kindModifiers?.includes('deprecated') ? [vscode_languageserver_1.SymbolTag.Deprecated] : undefined
};
}
getNameAndDetailForItem(useFileName, item) {
const nearestRootUri = (0, utils_2.getNearestWorkspaceUri)(this.workspaceUris, item.file, (0, utils_1.createGetCanonicalFileName)(typescript_1.default.sys.useCaseSensitiveFileNames));
const nearestRoot = nearestRootUri && ((0, utils_1.urlToPath)(nearestRootUri) ?? undefined);
const name = useFileName ? (0, path_1.basename)(item.file) : item.name;
const detail = useFileName
? nearestRoot && path_1.default.relative(nearestRoot, (0, path_1.dirname)(item.file))
: item.containerName;
return { name, detail };
}
async getIncomingCalls(previousItem, cancellationToken) {
const prepareResult = await this.prepareFurtherCalls(previousItem, cancellationToken);
if (!prepareResult) {
return null;
}
const { lang, filePath, program, snapshots, isComponentModulePosition, tsDoc, getNonComponentOffset } = prepareResult;
const componentExportOffset = isComponentModulePosition && tsDoc instanceof DocumentSnapshot_1.SvelteDocumentSnapshot
? (0, utils_2.offsetOfGeneratedComponentExport)(tsDoc)
: -1;
const offset = componentExportOffset >= 0 ? componentExportOffset : getNonComponentOffset();
const incomingCalls = lang
.provideCallHierarchyIncomingCalls(filePath, offset)
.concat(this.getInComingCallsForComponent(lang, program, filePath, offset) ?? []);
const result = await Promise.all(incomingCalls.map(async (item) => {
const snapshot = await snapshots.retrieve(item.from.file);
const from = await this.convertCallHierarchyItem(snapshots, item.from, program);
if (!from) {
return null;
}
return {
from,
fromRanges: this.convertFromRanges(snapshot, item.fromSpans)
};
}));
return result.filter(utils_1.isNotNullOrUndefined);
}
async getOutgoingCalls(previousItem, cancellationToken) {
const prepareResult = await this.prepareFurtherCalls(previousItem, cancellationToken);
if (!prepareResult) {
return null;
}
const { lang, filePath, program, snapshots, isComponentModulePosition, tsDoc, getNonComponentOffset } = prepareResult;
const sourceFile = program?.getSourceFile(filePath);
const renderFunctionOffset = isComponentModulePosition && tsDoc instanceof DocumentSnapshot_1.SvelteDocumentSnapshot && sourceFile
? sourceFile.statements
.find((statement) => typescript_1.default.isFunctionDeclaration(statement) &&
statement.name?.getText() === svelte2tsx_1.internalHelpers.renderName)
?.name?.getStart()
: -1;
const offset = renderFunctionOffset != null && renderFunctionOffset >= 0
? renderFunctionOffset
: getNonComponentOffset();
const outgoingCalls = lang
.provideCallHierarchyOutgoingCalls(filePath, offset)
.concat(isComponentModulePosition
? (this.getOutgoingCallsForComponent(program, filePath) ?? [])
: []);
const result = await Promise.all(outgoingCalls.map(async (item) => {
if (item.to.name.startsWith('__sveltets') ||
item.to.containerName === 'svelteHTML') {
return null;
}
const to = await this.convertCallHierarchyItem(snapshots, item.to, program);
if (!to) {
return null;
}
return {
to,
fromRanges: this.convertFromRanges(tsDoc, item.fromSpans)
};
}));
return result.filter(utils_1.isNotNullOrUndefined).filter((item) => item.fromRanges.length);
}
async prepareFurtherCalls(item, cancellationToken) {
const filePath = (0, utils_1.urlToPath)(item.uri);
if (!filePath) {
return null;
}
const lsContainer = await this.lsAndTsDocResolver.getTSService(filePath);
const lang = lsContainer.getService();
const tsDoc = await this.lsAndTsDocResolver.getOrCreateSnapshot(filePath);
if (cancellationToken?.isCancellationRequested) {
return null;
}
const program = lang.getProgram();
const snapshots = new utils_3.SnapshotMap(this.lsAndTsDocResolver, lsContainer);
snapshots.set(tsDoc.filePath, tsDoc);
const isComponentModulePosition = (0, utils_2.isSvelteFilePath)(item.name) &&
item.selectionRange.start.line === 0 &&
item.range.start.line === 0;
return {
snapshots,
filePath,
program,
tsDoc,
lang,
isComponentModulePosition,
getNonComponentOffset: () => tsDoc.offsetAt(tsDoc.getGeneratedPosition(item.selectionRange.start))
};
}
redirectCallHierarchyItem(snapshot, program, item) {
if (!(0, utils_2.isSvelteFilePath)(item.file) ||
!program ||
!(snapshot instanceof DocumentSnapshot_1.SvelteDocumentSnapshot)) {
return null;
}
const sourceFile = program.getSourceFile(item.file);
if (!sourceFile) {
return null;
}
if ((0, utils_2.isGeneratedSvelteComponentName)(item.name)) {
return this.toComponentCallHierarchyItem(snapshot, item);
}
if (item.name === svelte2tsx_1.internalHelpers.renderName) {
const end = item.selectionSpan.start + item.selectionSpan.length;
const renderFunction = sourceFile.statements.find((statement) => statement.getStart() <= item.selectionSpan.start && statement.getEnd() >= end);
if (!renderFunction || !sourceFile.statements.includes(renderFunction)) {
return null;
}
return this.toComponentCallHierarchyItem(snapshot, item);
}
return null;
}
toComponentCallHierarchyItem(snapshot, item) {
const fileStartPosition = vscode_languageserver_types_1.Position.create(0, 0);
const fileRange = vscode_languageserver_1.Range.create(fileStartPosition, snapshot.parent.positionAt(snapshot.parent.getTextLength()));
return {
...this.getNameAndDetailForItem(true, item),
kind: vscode_languageserver_1.SymbolKind.Module,
range: fileRange,
selectionRange: vscode_languageserver_1.Range.create(fileStartPosition, fileStartPosition),
uri: (0, utils_1.pathToUrl)(item.file)
};
}
convertFromRanges(snapshot, spans) {
return spans
.map((item) => (0, documents_1.mapRangeToOriginal)(snapshot, (0, utils_2.convertRange)(snapshot, item)))
.filter((range) => range.start.line >= 0 && range.end.line >= 0);
}
getInComingCallsForComponent(lang, program, filePath, offset) {
if (!program || !(0, utils_2.isSvelteFilePath)(filePath)) {
return null;
}
const groups = lang
.findReferences(filePath, offset)
?.map((entry) => [
entry.definition.fileName,
entry.references
.map((ref) => this.getComponentStartTagFromReference(program, ref))
.filter(utils_1.isNotNullOrUndefined)
])
.filter(([_, group]) => group.length);
return (groups?.map(([file, group]) => ({
from: {
file,
kind: typescript_1.default.ScriptElementKind.scriptElement,
name: (0, utils_2.toGeneratedSvelteComponentName)(''),
// doesn't matter, will be override later
selectionSpan: { start: 0, length: 0 },
span: { start: 0, length: 0 }
},
fromSpans: group.map((g) => g.textSpan)
})) ?? null);
}
getComponentStartTagFromReference(program, ref) {
const sourceFile = program.getSourceFile(ref.fileName);
if (!sourceFile) {
return null;
}
const node = (0, utils_3.findNodeAtSpan)(sourceFile, ref.textSpan, this.isComponentStartTag);
if (node) {
return ref;
}
return null;
}
isComponentStartTag(node) {
return (!!node &&
node.parent &&
typescript_1.default.isCallExpression(node.parent) &&
typescript_1.default.isIdentifier(node.parent.expression) &&
node.parent.expression.text === ENSURE_COMPONENT_HELPER &&
typescript_1.default.isIdentifier(node) &&
node === node.parent.arguments[0]);
}
getOutgoingCallsForComponent(program, filePath) {
const sourceFile = program?.getSourceFile(filePath);
if (!program || !sourceFile) {
return null;
}
const groups = new Map();
const startTags = (0, utils_3.gatherDescendants)(sourceFile, this.isComponentStartTag);
const typeChecker = program.getTypeChecker();
for (const startTag of startTags) {
const type = typeChecker.getTypeAtLocation(startTag);
const symbol = type.aliasSymbol ?? type.symbol;
const declaration = symbol?.valueDeclaration ?? symbol?.declarations?.[0];
if (!declaration || !typescript_1.default.isClassDeclaration(declaration)) {
continue;
}
let group = groups.get(declaration);
if (!group) {
group = [];
groups.set(declaration, group);
}
group.push({ start: startTag.getStart(), length: startTag.getWidth() });
}
return (Array.from(groups).map(([declaration, group]) => {
const file = declaration.getSourceFile().fileName;
const name = declaration.name?.getText() ?? (0, path_1.basename)(file);
const span = { start: declaration.getStart(), length: declaration.getWidth() };
const selectionSpan = declaration.name
? { start: declaration.name.getStart(), length: declaration.name.getWidth() }
: span;
return {
to: {
file,
kind: typescript_1.default.ScriptElementKind.classElement,
name,
selectionSpan,
span
},
fromSpans: group
};
}) ?? null);
}
}
exports.CallHierarchyProviderImpl = CallHierarchyProviderImpl;
//# sourceMappingURL=CallHierarchyProvider.js.map