svelte-language-server
Version:
A language server for Svelte
225 lines • 11.3 kB
JavaScript
"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