svelte-language-server
Version:
A language server for Svelte
331 lines • 13.5 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.gatherIdentifiers = exports.isInReactiveStatement = exports.isReactiveStatement = exports.SnapshotMap = exports.IGNORE_POSITION_COMMENT = exports.IGNORE_END_COMMENT = exports.IGNORE_START_COMMENT = void 0;
exports.getComponentAtPosition = getComponentAtPosition;
exports.isComponentAtPosition = isComponentAtPosition;
exports.surroundWithIgnoreComments = surroundWithIgnoreComments;
exports.isInGeneratedCode = isInGeneratedCode;
exports.startsWithIgnoredPosition = startsWithIgnoredPosition;
exports.isTextSpanInGeneratedCode = isTextSpanInGeneratedCode;
exports.isPartOfImportStatement = isPartOfImportStatement;
exports.isStoreVariableIn$storeDeclaration = isStoreVariableIn$storeDeclaration;
exports.get$storeOffsetOf$storeDeclaration = get$storeOffsetOf$storeDeclaration;
exports.is$storeVariableIn$storeDeclaration = is$storeVariableIn$storeDeclaration;
exports.getStoreOffsetOf$storeDeclaration = getStoreOffsetOf$storeDeclaration;
exports.isAfterSvelte2TsxPropsReturn = isAfterSvelte2TsxPropsReturn;
exports.findContainingNode = findContainingNode;
exports.findClosestContainingNode = findClosestContainingNode;
exports.findNodeAtSpan = findNodeAtSpan;
exports.findRenderFunction = findRenderFunction;
exports.gatherDescendants = gatherDescendants;
exports.isKitTypePath = isKitTypePath;
exports.getFormatCodeBasis = getFormatCodeBasis;
exports.getQuotePreference = getQuotePreference;
exports.findChildOfKind = findChildOfKind;
exports.getNewScriptStartTag = getNewScriptStartTag;
exports.checkRangeMappingWithGeneratedSemi = checkRangeMappingWithGeneratedSemi;
const typescript_1 = __importDefault(require("typescript"));
const documents_1 = require("../../../lib/documents");
const ComponentInfoProvider_1 = require("../ComponentInfoProvider");
const utils_1 = require("../../../utils");
const fileCollection_1 = require("../../../lib/documents/fileCollection");
const svelte2tsx_1 = require("svelte2tsx");
/**
* If the given original position is within a Svelte starting tag,
* return the snapshot of that component.
*/
function getComponentAtPosition(lang, doc, tsDoc, originalPosition) {
if (tsDoc.parserError) {
return null;
}
if ((0, documents_1.isInTag)(originalPosition, doc.scriptInfo) ||
(0, documents_1.isInTag)(originalPosition, doc.moduleScriptInfo)) {
// Inside script tags -> not a component
return null;
}
const node = (0, documents_1.getNodeIfIsInComponentStartTag)(doc.html, doc, doc.offsetAt(originalPosition));
if (!node) {
return null;
}
const symbolPosWithinNode = node.tag?.includes('.') ? node.tag.lastIndexOf('.') + 1 : 0;
const generatedPosition = tsDoc.getGeneratedPosition(doc.positionAt(node.start + symbolPosWithinNode + 1));
const def = lang.getDefinitionAtPosition(tsDoc.filePath, tsDoc.offsetAt(generatedPosition))?.[0];
if (!def) {
return null;
}
return ComponentInfoProvider_1.JsOrTsComponentInfoProvider.create(lang, def, tsDoc.isSvelte5Plus);
}
function isComponentAtPosition(doc, tsDoc, originalPosition) {
if (tsDoc.parserError) {
return false;
}
if ((0, documents_1.isInTag)(originalPosition, doc.scriptInfo) ||
(0, documents_1.isInTag)(originalPosition, doc.moduleScriptInfo)) {
// Inside script tags -> not a component
return false;
}
return !!(0, documents_1.getNodeIfIsInComponentStartTag)(doc.html, doc, doc.offsetAt(originalPosition));
}
exports.IGNORE_START_COMMENT = '/*Ωignore_startΩ*/';
exports.IGNORE_END_COMMENT = '/*Ωignore_endΩ*/';
exports.IGNORE_POSITION_COMMENT = '/*Ωignore_positionΩ*/';
/**
* Surrounds given string with a start/end comment which marks it
* to be ignored by tooling.
*/
function surroundWithIgnoreComments(str) {
return exports.IGNORE_START_COMMENT + str + exports.IGNORE_END_COMMENT;
}
/**
* Checks if this a section that should be completely ignored
* because it's purely generated.
*/
function isInGeneratedCode(text, start, end = start) {
const lastStart = text.lastIndexOf(exports.IGNORE_START_COMMENT, start);
const lastEnd = text.lastIndexOf(exports.IGNORE_END_COMMENT, start);
const nextEnd = text.indexOf(exports.IGNORE_END_COMMENT, end);
// if lastEnd === nextEnd, this means that the str was found at the index
// up to which is searched for it
return (lastStart > lastEnd || lastEnd === nextEnd) && lastStart < nextEnd;
}
function startsWithIgnoredPosition(text, offset) {
return text.slice(offset).startsWith(exports.IGNORE_POSITION_COMMENT);
}
/**
* Checks if this is a text span that is inside svelte2tsx-generated code
* (has no mapping to the original)
*/
function isTextSpanInGeneratedCode(text, span) {
return isInGeneratedCode(text, span.start, span.start + span.length);
}
function isPartOfImportStatement(text, position) {
const line = (0, documents_1.getLineAtPosition)(position, text);
return /\s*from\s+["'][^"']*/.test(line.slice(0, position.character));
}
function isStoreVariableIn$storeDeclaration(text, varStart) {
return (text.lastIndexOf('__sveltets_2_store_get(', varStart) ===
varStart - '__sveltets_2_store_get('.length);
}
function get$storeOffsetOf$storeDeclaration(text, storePosition) {
return text.lastIndexOf(' =', storePosition) - 1;
}
function is$storeVariableIn$storeDeclaration(text, varStart) {
return /^\$\w+ = __sveltets_2_store_get/.test(text.substring(varStart));
}
function getStoreOffsetOf$storeDeclaration(text, $storeVarStart) {
return text.indexOf(');', $storeVarStart) - 1;
}
class SnapshotMap {
constructor(resolver, sourceLs) {
this.resolver = resolver;
this.sourceLs = sourceLs;
this.map = new fileCollection_1.FileMap();
}
set(fileName, snapshot) {
this.map.set(fileName, snapshot);
}
get(fileName) {
return this.map.get(fileName);
}
async retrieve(fileName) {
let snapshot = this.get(fileName);
if (snapshot) {
return snapshot;
}
const snap = this.sourceLs.snapshotManager.get(fileName) ??
// should not happen in most cases,
// the file should be in the project otherwise why would we know about it
(await this.resolver.getOrCreateSnapshot(fileName));
this.set(fileName, snap);
return snap;
}
}
exports.SnapshotMap = SnapshotMap;
function isAfterSvelte2TsxPropsReturn(text, end) {
const textBeforeProp = text.substring(0, end);
// This is how svelte2tsx writes out the props
if (textBeforeProp.includes('\nreturn { props: {')) {
return true;
}
}
function findContainingNode(node, textSpan, predicate) {
const children = node.getChildren();
const end = textSpan.start + textSpan.length;
for (const child of children) {
if (!(child.getStart() <= textSpan.start && child.getEnd() >= end)) {
continue;
}
if (predicate(child)) {
return child;
}
const foundInChildren = findContainingNode(child, textSpan, predicate);
if (foundInChildren) {
return foundInChildren;
}
}
}
function findClosestContainingNode(node, textSpan, predicate) {
let current = findContainingNode(node, textSpan, predicate);
if (!current) {
return;
}
let closest = current;
while (current) {
const foundInChildren = findContainingNode(current, textSpan, predicate);
closest = current;
current = foundInChildren;
}
return closest;
}
/**
* Finds node exactly matching span {start, length}.
*/
function findNodeAtSpan(node, span, predicate) {
const { start, length } = span;
const end = start + length;
for (const child of node.getChildren()) {
const childStart = child.getStart();
if (end <= childStart) {
return;
}
const childEnd = child.getEnd();
if (start >= childEnd) {
continue;
}
if (start === childStart && end === childEnd) {
if (!predicate) {
return child;
}
if (predicate(child)) {
return child;
}
}
const foundInChildren = findNodeAtSpan(child, span, predicate);
if (foundInChildren) {
return foundInChildren;
}
}
}
function isSomeAncestor(node, predicate) {
for (let parent = node.parent; parent; parent = parent.parent) {
if (predicate(parent)) {
return true;
}
}
return false;
}
/**
* Tests a node then its parent and successive ancestors for some respective predicates.
*/
function nodeAndParentsSatisfyRespectivePredicates(selfPredicate, ...predicates) {
return (node) => {
let next = node;
return [selfPredicate, ...predicates].every((predicate) => {
if (!next) {
return false;
}
const current = next;
next = next.parent;
return predicate(current);
});
};
}
const isRenderFunction = nodeAndParentsSatisfyRespectivePredicates((node) => typescript_1.default.isFunctionDeclaration(node) && node?.name?.getText() === svelte2tsx_1.internalHelpers.renderName, typescript_1.default.isSourceFile);
const isRenderFunctionBody = nodeAndParentsSatisfyRespectivePredicates(typescript_1.default.isBlock, isRenderFunction);
exports.isReactiveStatement = nodeAndParentsSatisfyRespectivePredicates((node) => typescript_1.default.isLabeledStatement(node) && node.label.getText() === '$', (0, utils_1.or)(
// function $$render() {
// $: x2 = __sveltets_2_invalidate(() => x * x)
// }
isRenderFunctionBody,
// function $$render() {
// ;() => {$: x, update();
// }
nodeAndParentsSatisfyRespectivePredicates(typescript_1.default.isBlock, typescript_1.default.isArrowFunction, typescript_1.default.isExpressionStatement, isRenderFunctionBody)));
function findRenderFunction(sourceFile) {
// only search top level
for (const child of sourceFile.statements) {
if (isRenderFunction(child)) {
return child;
}
}
}
const isInReactiveStatement = (node) => isSomeAncestor(node, exports.isReactiveStatement);
exports.isInReactiveStatement = isInReactiveStatement;
function gatherDescendants(node, predicate, dest = []) {
if (predicate(node)) {
dest.push(node);
}
else {
for (const child of node.getChildren()) {
gatherDescendants(child, predicate, dest);
}
}
return dest;
}
const gatherIdentifiers = (node) => gatherDescendants(node, typescript_1.default.isIdentifier);
exports.gatherIdentifiers = gatherIdentifiers;
function isKitTypePath(path) {
return !!path?.includes('.svelte-kit/types');
}
function getFormatCodeBasis(formatCodeSetting) {
const { baseIndentSize, indentSize, convertTabsToSpaces } = formatCodeSetting;
const baseIndent = convertTabsToSpaces
? ' '.repeat(baseIndentSize ?? 4)
: baseIndentSize
? '\t'
: '';
const indent = convertTabsToSpaces ? ' '.repeat(indentSize ?? 4) : baseIndentSize ? '\t' : '';
const semi = formatCodeSetting.semicolons === 'remove' ? '' : ';';
const newLine = formatCodeSetting.newLineCharacter ?? typescript_1.default.sys.newLine;
return {
baseIndent,
indent,
semi,
newLine
};
}
/**
* https://github.com/microsoft/TypeScript/blob/00dc0b6674eef3fbb3abb86f9d71705b11134446/src/services/utilities.ts#L2452
*/
function getQuotePreference(sourceFile, preferences) {
const single = "'";
const double = '"';
if (preferences.quotePreference && preferences.quotePreference !== 'auto') {
return preferences.quotePreference === 'single' ? single : double;
}
const firstModuleSpecifier = Array.from(sourceFile.statements).find((statement) => typescript_1.default.isImportDeclaration(statement) && typescript_1.default.isStringLiteral(statement.moduleSpecifier))?.moduleSpecifier;
return firstModuleSpecifier
? sourceFile.getText()[firstModuleSpecifier.pos] === '"'
? double
: single
: double;
}
function findChildOfKind(node, kind) {
for (const child of node.getChildren()) {
if (child.kind === kind) {
return child;
}
const foundInChildren = findChildOfKind(child, kind);
if (foundInChildren) {
return foundInChildren;
}
}
}
function getNewScriptStartTag(lsConfig, newLine) {
const lang = lsConfig.svelte.defaultScriptLanguage;
const scriptLang = lang === 'none' ? '' : ` lang="${lang}"`;
return `<script${scriptLang}>${newLine}`;
}
function checkRangeMappingWithGeneratedSemi(originalRange, generatedRange, tsDoc) {
const originalLength = originalRange.end.character - originalRange.start.character;
const generatedLength = generatedRange.end.character - generatedRange.start.character;
// sourcemap off by one character issue + a generated semicolon
if (originalLength === generatedLength - 2 &&
tsDoc.getFullText()[tsDoc.offsetAt(generatedRange.end) - 1] === ';') {
originalRange.end.character += 1;
}
}
//# sourceMappingURL=utils.js.map