UNPKG

svelte-language-server

Version:
325 lines 15.4 kB
"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