UNPKG

svelte-language-server

Version:
225 lines 11.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.InlayHintProviderImpl = void 0; const typescript_1 = __importDefault(require("typescript")); const vscode_languageserver_types_1 = require("vscode-languageserver-types"); const documents_1 = require("../../../lib/documents"); const parseHtml_1 = require("../../../lib/documents/parseHtml"); const utils_1 = require("./utils"); const utils_2 = require("../utils"); class InlayHintProviderImpl { constructor(lsAndTsDocResolver) { this.lsAndTsDocResolver = lsAndTsDocResolver; } async getInlayHints(document, range, cancellationToken) { // Don't sync yet so we can skip TypeScript's synchronizeHostData if inlay hints are disabled const { userPreferences } = await this.lsAndTsDocResolver.getLsForSyntheticOperations(document); if (cancellationToken?.isCancellationRequested || !this.areInlayHintsEnabled(userPreferences)) { return null; } const { tsDoc, lang, lsContainer } = await this.lsAndTsDocResolver.getLSAndTSDoc(document); const inlayHints = lang.provideInlayHints(tsDoc.filePath, this.convertToTargetTextSpan(range, tsDoc), userPreferences); const sourceFile = lang.getProgram()?.getSourceFile(tsDoc.filePath); if (!sourceFile) { return []; } const renderFunction = (0, utils_1.findRenderFunction)(sourceFile); const renderFunctionReturnTypeLocation = renderFunction && this.getTypeAnnotationPosition(renderFunction); const snapshotMap = new utils_1.SnapshotMap(this.lsAndTsDocResolver, lsContainer); snapshotMap.set(tsDoc.filePath, tsDoc); const convertPromises = inlayHints .filter((inlayHint) => !(0, utils_1.isInGeneratedCode)(tsDoc.getFullText(), inlayHint.position) && inlayHint.position !== renderFunctionReturnTypeLocation && !this.isSvelte2tsxFunctionHints(sourceFile, inlayHint) && !this.isGeneratedVariableTypeHint(sourceFile, inlayHint) && !this.isGeneratedAsyncFunctionReturnType(sourceFile, inlayHint) && !this.isGeneratedFunctionReturnType(sourceFile, inlayHint)) .map(async (inlayHint) => ({ label: await this.convertInlayHintLabelParts(inlayHint, snapshotMap), position: this.getOriginalPosition(document, tsDoc, inlayHint), kind: this.convertInlayHintKind(inlayHint.kind), paddingLeft: inlayHint.whitespaceBefore, paddingRight: inlayHint.whitespaceAfter })); return (await Promise.all(convertPromises)).filter((inlayHint) => inlayHint.position.line >= 0 && inlayHint.position.character >= 0 && !this.checkGeneratedFunctionHintWithSource(inlayHint, document)); } areInlayHintsEnabled(preferences) { return (preferences.includeInlayParameterNameHints === 'literals' || preferences.includeInlayParameterNameHints === 'all' || preferences.includeInlayEnumMemberValueHints || preferences.includeInlayFunctionLikeReturnTypeHints || preferences.includeInlayFunctionParameterTypeHints || preferences.includeInlayPropertyDeclarationTypeHints || preferences.includeInlayVariableTypeHints); } convertToTargetTextSpan(range, snapshot) { const generatedStartOffset = snapshot.getGeneratedPosition(range.start); const generatedEndOffset = snapshot.getGeneratedPosition(range.end); const start = generatedStartOffset.line < 0 ? 0 : snapshot.offsetAt(generatedStartOffset); const end = generatedEndOffset.line < 0 ? snapshot.getLength() : snapshot.offsetAt(generatedEndOffset); return { start, length: end - start }; } async convertInlayHintLabelParts(inlayHint, snapshotMap) { if (!inlayHint.displayParts) { return inlayHint.text; } const convertPromises = inlayHint.displayParts.map(async (part) => { if (!part.file || !part.span) { return { value: part.text }; } const snapshot = await snapshotMap.retrieve(part.file); if (!snapshot) { return { value: part.text }; } const originalLocation = (0, documents_1.mapLocationToOriginal)(snapshot, (0, utils_2.convertRange)(snapshot, part.span)); return { value: part.text, location: originalLocation.range.start.line < 0 ? undefined : originalLocation }; }); const parts = await Promise.all(convertPromises); return parts; } getOriginalPosition(document, tsDoc, inlayHint) { let originalPosition = tsDoc.getOriginalPosition(tsDoc.positionAt(inlayHint.position)); if (inlayHint.kind === typescript_1.default.InlayHintKind.Type) { const originalOffset = document.offsetAt(originalPosition); const source = document.getText(); // detect if inlay hint position is off by one // by checking if source[offset] is part of an identifier // https://github.com/sveltejs/language-tools/pull/2070 if (originalOffset < source.length && !/[\x00-\x23\x25-\x2F\x3A-\x40\x5B\x5D-\x5E\x60\x7B-\x7F]/.test(source[originalOffset])) { originalPosition.character += 1; } } return originalPosition; } convertInlayHintKind(kind) { switch (kind) { case 'Parameter': return vscode_languageserver_types_1.InlayHintKind.Parameter; case 'Type': return vscode_languageserver_types_1.InlayHintKind.Type; case 'Enum': return undefined; default: return undefined; } } isSvelte2tsxFunctionHints(sourceFile, inlayHint) { if (inlayHint.kind !== typescript_1.default.InlayHintKind.Parameter) { return false; } if (inlayHint.displayParts?.some((v) => (0, utils_2.isSvelte2tsxShimFile)(v.file))) { return true; } const hasParameterWithSamePosition = (node) => node.arguments !== undefined && node.arguments.some((arg) => arg.getStart() === inlayHint.position); const node = (0, utils_1.findContainingNode)(sourceFile, { start: inlayHint.position, length: 0 }, (node) => typescript_1.default.isCallOrNewExpression(node) && hasParameterWithSamePosition(node)); if (!node) { return false; } const expressionText = node.expression.getText(); const isComponentEventHandler = expressionText.includes('.$on'); return (isComponentEventHandler || expressionText.includes('.createElement') || expressionText.includes('__sveltets_') || expressionText.startsWith('$$_')); } isGeneratedVariableTypeHint(sourceFile, inlayHint) { if (inlayHint.kind !== typescript_1.default.InlayHintKind.Type) { return false; } if ((0, utils_1.startsWithIgnoredPosition)(sourceFile.text, inlayHint.position)) { return true; } const declaration = (0, utils_1.findContainingNode)(sourceFile, { start: inlayHint.position, length: 0 }, typescript_1.default.isVariableDeclaration); if (!declaration) { return false; } // $$_tnenopmoC, $$_value, $$props, $$slots, $$restProps... return ((0, utils_1.isInGeneratedCode)(sourceFile.text, declaration.pos) || declaration.name.getText().startsWith('$$')); } /** `true` if is one of the `async () => {...}` functions svelte2tsx generates */ isGeneratedAsyncFunctionReturnType(sourceFile, inlayHint) { if (inlayHint.kind !== typescript_1.default.InlayHintKind.Type) { return false; } const expression = (0, utils_1.findContainingNode)(sourceFile, { start: inlayHint.position, length: 0 }, (node) => typescript_1.default.isArrowFunction(node)); if (!expression?.modifiers?.some((m) => m.kind === typescript_1.default.SyntaxKind.AsyncKeyword) || !expression.parent?.parent || !typescript_1.default.isBlock(expression.parent.parent)) { return false; } return this.getTypeAnnotationPosition(expression) === inlayHint.position; } isGeneratedFunctionReturnType(sourceFile, inlayHint) { if (inlayHint.kind !== typescript_1.default.InlayHintKind.Type) { return false; } // $: a = something // it's always top level and shouldn't be under other function call // so we don't need to use findClosestContainingNode const expression = (0, utils_1.findContainingNode)(sourceFile, { start: inlayHint.position, length: 0 }, (node) => typescript_1.default.isCallExpression(node) && typescript_1.default.isIdentifier(node.expression)); if (!expression) { return false; } return (expression.expression.text === '__sveltets_2_invalidate' && typescript_1.default.isArrowFunction(expression.arguments[0]) && this.getTypeAnnotationPosition(expression.arguments[0]) === inlayHint.position); } getTypeAnnotationPosition(decl) { const closeParenToken = (0, utils_1.findChildOfKind)(decl, typescript_1.default.SyntaxKind.CloseParenToken); if (closeParenToken) { return closeParenToken.end; } return decl.parameters.end; } checkGeneratedFunctionHintWithSource(inlayHint, document) { if ((0, documents_1.isInTag)(inlayHint.position, document.moduleScriptInfo)) { return false; } if ((0, documents_1.isInTag)(inlayHint.position, document.scriptInfo)) { return document .getText() .slice(document.offsetAt(inlayHint.position)) .trimStart() .startsWith('$:'); } const attributeContext = (0, parseHtml_1.getAttributeContextAtPosition)(document, inlayHint.position); if (!attributeContext || attributeContext.inValue || !attributeContext.name.includes(':')) { return false; } const { name, elementTag } = attributeContext; // <div on:click> if (name.startsWith('on:') && !elementTag.attributes?.[attributeContext.name]) { return true; } const directives = ['in', 'out', 'animate', 'transition', 'use']; // hide // - transitionCall: for __sveltets_2_ensureTransition // - tag: for svelteHTML.mapElementTag inside transition call and action call // - animationCall: for __sveltets_2_ensureAnimation // - actionCall for __sveltets_2_ensureAction return directives.some((directive) => name.startsWith(directive + ':')); } } exports.InlayHintProviderImpl = InlayHintProviderImpl; //# sourceMappingURL=InlayHintProvider.js.map