UNPKG

qingkuai-language-service

Version:

The language service for qingkuai

1,360 lines (1,346 loc) 69.8 kB
import { a as isUndefined, g as traverseObject, L as LSU_AND_DOT, G as GLOBAL_TYPE_IDS, v as GlobalTypeIsNonObjectJs, w as recoverNumberArray, x as recoverPositions, y as compressNumberArray, z as isQingkuaiFileName, I as INVALID_COMPLETION_TEXT_LABELS, A as INVLALID_COMPLETION_PACKAGES, d as debugAssert, B as qkExtInImportRE, C as CompletionImportTextEditRE, c as isIndexesInvalid, D as SCRIPT_EXTENSIONS, m as mdCodeBlockGen, P as PROXIED_MARK, i as isEmptyString, E as SOURCE_SPAN_MARK, b as isString } from './chunks/shared.js'; import { constants, util, PositionFlag } from 'qingkuai/compiler'; let ts; function setState(options) { ({ ts } = options); } function isInTopScope(node) { switch (node.parent.kind) { case void 0: case ts.SyntaxKind.SourceFile: { return true; } case ts.SyntaxKind.Block: case ts.SyntaxKind.CaseBlock: case ts.SyntaxKind.ModuleBlock: case ts.SyntaxKind.ForStatement: case ts.SyntaxKind.ForInStatement: case ts.SyntaxKind.ForOfStatement: case ts.SyntaxKind.ClassStaticBlockDeclaration: { return false; } default: { return !ts.isParameter(node) && isInTopScope(node.parent); } } } function isInComponentFunctionTopScope(node) { const blockNode = ts.findAncestor(node, (ancestor) => { return ts.isBlock(ancestor); }); return !!(blockNode?.parent && ts.isFunctionDeclaration(blockNode.parent) && blockNode.parent.name?.text === constants.LSC.COMPONENT); } function walkTsNode(node, callback) { if (!isUndefined(node)) { callback(node); ts.forEachChild(node, (cn) => { walkTsNode(cn, callback); }); } } function getNodeAtPositionAndWithin(node, pos) { const [start, len] = [node.getStart(), node.getWidth()]; if (pos >= start && pos <= start + len) { for (const child of node.getChildren()) { const foundInChild = getNodeAtPositionAndWithin(child, pos); if (foundInChild) { return foundInChild; } } return node; } return void 0; } const ExternalGlobalTypeWithGenerics = () => { return [ 3001, `The external type with generics cannot be used as the global type.`, "https://qingkuai.dev/misc/typescript.html#generics" ]; }; const GlobalTypeIsNonObjectTs = (name) => { return [ 3002, `The global type "${name}" must satisfy the constraint of being an object type.`, "https://qingkuai.dev/misc/typescript.html#component-attribute-types" ]; }; var __defProp$3 = Object.defineProperty; var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField$3 = (obj, key, value) => __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value); function confirmTypesForCompileResult(adapter, fileInfo) { let sourceFile; let typeChecker; let componentFuncNode; let componentReturnsNode; const updateSourceFile = () => { const program = adapter.getDefaultProgram(fileInfo.path); sourceFile = program?.getSourceFile(fileInfo.path); typeChecker = program?.getTypeChecker(); return sourceFile; }; if (fileInfo.typesConfirmed || !updateSourceFile()) { return; } fileInfo.typesConfirmed = true; const componentGenerics = []; const slotNames = []; const edit = new FileEdit(fileInfo, updateSourceFile); const globalTypes = {}; const extractedSlotContexts = []; const anyValueStr = constants.LSC.UTIL + ".anyValue"; const getTypeDelayIndexesSet = new Set(fileInfo.getTypeDelayIndexes); walkTsNode(sourceFile, (node) => { var _a; if (ts.isFunctionDeclaration(node) && node.name?.text === constants.LSC.COMPONENT && isInTopScope(node)) { componentFuncNode = node; } if (!fileInfo.isTS) { const jsDocs = node.jsDoc; jsDocs?.forEach((jsDoc) => { for (const jsDocTag of jsDoc.tags ?? []) { if (ts.isJSDocTypedefTag(jsDocTag) && jsDocTag.name?.text && GLOBAL_TYPE_IDS.has(jsDocTag.name.text) && !globalTypes[jsDocTag.name.text] && isInComponentFunctionTopScope(jsDocTag)) { const constraints = []; const genericNames = []; const globalType = typeChecker.getTypeAtLocation(jsDocTag); const templateTags = jsDocTag.parent.tags?.filter(ts.isJSDocTemplateTag); if (!(globalType.flags & ts.TypeFlags.Object)) { fileInfo.pushDiagnostic( jsDocTag.getStart(), jsDocTag.getEnd(), GlobalTypeIsNonObjectJs(jsDocTag.name.text) ); } templateTags?.forEach((tag) => { tag.typeParameters.forEach((param, index) => { const genericName = param.name.getText() + jsDocTag.name.text.slice(0, 1); const constraint = param.constraint ?? (index === 0 ? tag.constraint : void 0); const constraintText = constraint?.getText() ?? ""; if (constraintText) { constraints.push(constraintText); } genericNames.push(genericName); componentGenerics.push(`@template${constraintText} ${genericName}`); }); }); globalTypes[jsDocTag.name.text] = { constraints, genericNames, type: globalType, isExternal: false }; } } }); } else if ((ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)) && GLOBAL_TYPE_IDS.has(node.name.text) && !globalTypes[node.name.text] && isInComponentFunctionTopScope(node)) { const constraints = []; const genericNames = []; const globalType = typeChecker.getTypeAtLocation(node); if (!(globalType.flags & ts.TypeFlags.Object)) { fileInfo.pushDiagnostic( node.name.getStart(), node.name.getEnd(), GlobalTypeIsNonObjectTs(node.name.text) ); } node.typeParameters?.forEach((param) => { const genericName = param.name.getText() + node.name.text.slice(0, 1); if (param.constraint) { constraints.push(param.constraint.getText()); } componentGenerics.push( `${genericName}${param.constraint ? ` extends ${param.constraint.getText()}` : ""}` ); genericNames.push(genericName); }); globalTypes[node.name.text] = { genericNames, constraints, isExternal: false, type: typeChecker.getTypeAtLocation(node) }; } if (ts.isImportDeclaration(node)) { if (isInTopScope(node)) { const identifiers = []; if (ts.isImportDeclaration(node) && node.importClause) { if (node.importClause.name) { identifiers.push(node.importClause.name); } if (node.importClause.namedBindings && !ts.isNamespaceImport(node.importClause.namedBindings)) { for (const spec of node.importClause.namedBindings.elements) { identifiers.push(spec.name); } } } for (const id of identifiers) { if (!globalTypes[id.text] && GLOBAL_TYPE_IDS.has(id.text)) { const symbol = typeChecker.getSymbolAtLocation(id); const aliasedSymbol = symbol && typeChecker.getAliasedSymbol(symbol); if (aliasedSymbol && aliasedSymbol.flags & ts.SymbolFlags.Type) { const globalType = typeChecker.getTypeAtLocation( aliasedSymbol.declarations[0] ); if (!(globalType.flags & ts.TypeFlags.Object)) { fileInfo.pushDiagnostic( id.getStart(), id.getEnd(), GlobalTypeIsNonObjectTs(id.text) ); } else if (globalType.objectFlags & ts.ObjectFlags.Reference && typeChecker.getTypeArguments(globalType).length) { fileInfo.pushDiagnostic( id.getStart(), id.getEnd(), ExternalGlobalTypeWithGenerics() ); } globalTypes[id.text] = { isExternal: true, constraints: [], genericNames: [], type: globalType }; } } } } } if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.name) && ts.isIdentifier(node.expression.expression) && getTypeDelayIndexesSet.has(node.getStart()) && node.expression.getText() === constants.LSC.GET_TYPE_DELAY_MARKING) { const slotNameNode = node.arguments[0]; const contextPropertyNode = node.arguments[1]; if (slotNameNode.text !== slotNames[slotNames.length - 1]?.name) { slotNames.push({ name: slotNameNode.text, sourceRange: [ fileInfo.getSourceIndex(slotNameNode.getStart()), fileInfo.getSourceIndex(slotNameNode.getEnd()) ] }); fileInfo.slotNames.push(slotNameNode.text); } (extractedSlotContexts[_a = slotNames.length - 1] ?? (extractedSlotContexts[_a] = [])).push({ property: { name: contextPropertyNode.text, sourceRange: [ fileInfo.getSourceIndex(contextPropertyNode.getStart()), fileInfo.getSourceIndex(contextPropertyNode.getEnd()) ] }, valueType: typeChecker.typeToString( typeChecker.getTypeAtLocation(node.arguments[2]) ) }); } if (ts.isReturnStatement(node) && node.getEnd() === componentFuncNode.getEnd() - 2) { componentReturnsNode = node; } }); traverseObject(globalTypes, (kind, globalType) => { if (globalType && (kind === "Props" || kind === "Refs") && globalType.type.flags & ts.TypeFlags.Object) { for (const property of typeChecker.getPropertiesOfType(globalType.type)) { const propertyType = typeChecker.getTypeOfSymbolAtLocation(property, sourceFile); const attributeItem = { kind, name: property.name, stringCandidates: [], type: typeChecker.typeToString(propertyType), optional: !!(property.flags & ts.SymbolFlags.Optional), mayBeEvent: kind === "Props" && isMayBeEventType(propertyType), couldBeString: !!(propertyType.flags & ts.TypeFlags.StringLike) }; if (propertyType.isUnion()) { propertyType.types.forEach((t) => { if (t.flags & ts.TypeFlags.StringLiteral) { attributeItem.stringCandidates.push( JSON.parse(typeChecker.typeToString(t)) ); } attributeItem.couldBeString || (attributeItem.couldBeString = !!(t.flags & ts.TypeFlags.StringLike)); }); } else if (propertyType.flags & ts.TypeFlags.StringLiteral) { attributeItem.stringCandidates.push( JSON.parse(typeChecker.typeToString(propertyType)) ); } if (isEnumerablePropertyOfGlobalTypes(property)) { fileInfo.attributes.push(attributeItem); } } } }); ["Props", "Refs"].forEach((kind) => { const globalType = globalTypes[kind]; if (!globalType) { edit.setEditIndex(componentFuncNode.getStart()); if (fileInfo.isTS) { edit.push(`type ${kind} = ${constants.LSC.UTIL}.EmptyObject; `); } else { edit.push(`/** @typedef {${constants.LSC.UTIL}.EmptyObject} ${kind} */ `); } } }); if (fileInfo.isTS && !edit.isEmpty) { edit.flush(); } let contextRefsType = "Refs"; let declareRefsType = "Refs"; let contextPropsType = "Props"; let declarePropsType = "Props"; if (globalTypes.Refs?.constraints.length) { declareRefsType = `Refs<${globalTypes.Refs.constraints.join(", ")}>`; } if (globalTypes.Refs?.genericNames.length) { contextRefsType = `Refs<${globalTypes.Refs.genericNames.join(", ")}>`; } if (globalTypes.Props?.constraints.length) { declarePropsType = `Props<${globalTypes.Props.constraints.join(", ")}>`; } if (globalTypes.Props?.genericNames.length) { contextPropsType = `Props<${globalTypes.Props.genericNames.join(", ")}>`; } if (componentReturnsNode) { if (fileInfo.isTS) { edit.setEditIndex(componentFuncNode.body.getStart() + 2); edit.push(` const props: Readonly<${declarePropsType}> = ${anyValueStr}; `); edit.push(` const refs: ${declareRefsType} = ${anyValueStr}; `); edit.flush(); if (componentGenerics.length) { edit.setEditIndex(componentReturnsNode.getStart() + 7); edit.push(`<${componentGenerics.join(", ")}>`); edit.flush(); } edit.setEditIndex(componentReturnsNode.getStart() + 9); edit.push(`: { props: ${contextPropsType}; refs: ${contextRefsType}; slots: `); } else { edit.setEditIndex(componentFuncNode.body.getStart() + 2); edit.push( ` /** @type {Readonly<${declarePropsType}>} */ const props = ${anyValueStr}; ` ); edit.push(` /** @type {${declareRefsType}} */ const refs = ${anyValueStr}; `); edit.flush(); edit.push("/**\n"); for (const generic of componentGenerics) { edit.push(` * ${generic} `); } edit.setEditIndex(componentReturnsNode.getStart()); edit.push( ` * @param {Object} _ * @param {${contextPropsType}} _.props ` ); edit.push(` * @param {${contextRefsType}} _.refs * @param {`); } } if (!slotNames.length) { edit.push(`${LSU_AND_DOT}EmptyObject`); } else { edit.push("{ "); for (let i = 0; i < slotNames.length; i++) { edit.push(util.toPropertyKey(slotNames[i].name), slotNames[i].sourceRange); edit.push(": (context: { "); for (const { property, valueType } of extractedSlotContexts[i]) { edit.push(util.toPropertyKey(property.name), property.sourceRange); edit.push(`: ${valueType};`); } edit.push("}) => void;"); } edit.push(" }"); } if (fileInfo.isTS) { edit.push("}"); } else { edit.push("} _.slots\n */\n "); } edit.flush(); updateSourceFile(); const sourceFileSymbol = typeChecker.getSymbolAtLocation(sourceFile); const defaultExportSymbol = sourceFileSymbol.exports?.get(ts.InternalSymbolName.Default); if (defaultExportSymbol) { const defaultExportType = typeChecker.getTypeOfSymbolAtLocation( defaultExportSymbol, sourceFile ); const defaultExportTypeStr = typeChecker.typeToString(defaultExportType).replaceAll("{", "{\n"); fileInfo.defaultExportTypeStr = defaultExportTypeStr; } } class FileEdit { constructor(fileInfo, updateSourceFile) { this.fileInfo = fileInfo; this.updateSourceFile = updateSourceFile; __publicField$3(this, "index", -1); __publicField$3(this, "insertInfo", {}); __publicField$3(this, "items", []); } get isEmpty() { return this.items.length === 0; } get editStartIndex() { return this.getEditedIndex(this.index); } setEditIndex(index) { this.index = index; } getEditedIndex(index) { let ret = index; traverseObject(this.insertInfo, (key, value) => { if (key < index) { ret += value; } }); return ret; } push(content, sourceRange) { this.items.push({ content, sourceRange }); } flush() { var _a, _b; let newContent; const startIndex = this.editStartIndex; const originalContent = this.fileInfo.code; newContent = originalContent.slice(0, startIndex); for (const item of this.items) { newContent += item.content; } newContent += this.fileInfo.code.slice(startIndex); this.fileInfo.updateContent(newContent); this.fileInfo.adjustIndexMap(this); for (const item of this.items) { (_a = this.insertInfo)[_b = this.index] ?? (_a[_b] = 0); this.insertInfo[this.index] += item.content.length; } this.index = -1; this.items = []; } } function isMayBeEventType(type) { if (type.isClass()) { return false; } if (type.isUnion()) { return type.types.some((item) => isMayBeEventType(item)); } return !!(type.getCallSignatures().length || type.symbol?.name === "Function"); } function isEnumerablePropertyOfGlobalTypes(symbol) { if (symbol.declarations?.length !== 1) { return false; } const declaration = symbol.declarations[0]; if (!("name" in declaration)) { return false; } switch (declaration.name.kind) { case ts.SyntaxKind.Identifier: case ts.SyntaxKind.StringLiteral: case ts.SyntaxKind.NumericLiteral: { return true; } default: { return false; } } } var __defProp$2 = Object.defineProperty; var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value); class QingkuaiFileInfo { constructor(code, isTS, version, path, getTypeDelayIndexes, idStatusInfo, adapter, itos, stoi, positions) { this.code = code; this.isTS = isTS; this.version = version; this.path = path; this.getTypeDelayIndexes = getTypeDelayIndexes; this.idStatusInfo = idStatusInfo; this.adapter = adapter; this.itos = itos; this.stoi = stoi; this.positions = positions; __publicField$2(this, "nextAdjustSourceIndex", -1); __publicField$2(this, "isOpen", false); __publicField$2(this, "componentName"); __publicField$2(this, "typesConfirmed", false); __publicField$2(this, "slotNames", []); __publicField$2(this, "defaultExportTypeStr", ""); __publicField$2(this, "lsDiagnostics", []); __publicField$2(this, "attributes", []); this.componentName = filePathToComponentName(adapter, path); } getSourceIndex(interIndex) { return this.itos[interIndex]; } getInterIndex(sourceIndex) { return this.stoi[sourceIndex]; } getPositionByIndex(index) { return this.positions[index]; } updateContent(newContent) { this.adapter.updateContent(this, newContent); } confirmTypes() { confirmTypesForCompileResult(this.adapter, this); } isPositionFlagSetAtIndex(key, index) { return !!(this.positions[index].flag & PositionFlag[key]); } adjustIndexMap(edit) { const newItos = []; const editStartIndex = edit.editStartIndex; newItos.push(...this.itos.slice(0, editStartIndex)); for (let i = 0, j = editStartIndex; i < edit.items.length; i++) { const item = edit.items[i]; const contentLength = item.content.length; const [interStart, interEnd] = [j, j += contentLength]; for (let i2 = 0; i2 < this.stoi.length; i2++) { if (this.stoi[i2] > interStart) { this.stoi[i2] += contentLength; } } if (!item.sourceRange) { newItos.push(...Array(contentLength).fill(-1)); if (this.nextAdjustSourceIndex !== -1) { newItos[interStart] = this.nextAdjustSourceIndex; this.stoi[this.nextAdjustSourceIndex] = interStart; this.nextAdjustSourceIndex = -1; } continue; } const [sourceStart, sourceEnd] = item.sourceRange; this.stoi[sourceEnd] = interEnd; this.nextAdjustSourceIndex = sourceEnd; for (let i2 = 0; i2 < contentLength; i2++) { newItos.push(Math.min(sourceStart + i2, sourceEnd - 1)); } for (let i2 = 0; i2 < sourceEnd - sourceStart; i2++) { this.stoi[sourceStart + i2] = Math.min(interStart + i2, interEnd - 1); } } const left = this.itos.slice(editStartIndex); this.itos.length = 0; this.itos.push(...newItos, ...left); } pushDiagnostic(start, end, [code, message, link]) { const category = code >= 3e3 && code < 4e3 ? this.adapter.ts.DiagnosticCategory.Error : this.adapter.ts.DiagnosticCategory.Warning; this.lsDiagnostics.push({ code, category, url: link, start, source: "qk", length: end - start, messageText: message, file: this.adapter.getDefaultSourceFile(this.path) }); } } function updateQingkuaiFile(adapter, params) { const itos = recoverNumberArray(params.itos); const stoi = recoverNumberArray(params.stoi); const path = adapter.getNormalizedPath(params.fileName); const existing = adapter.qingkuaiFileInfos.get(path); const positions = recoverPositions(params.positions); const flags = recoverNumberArray(params.positionFlags); for (let i = 0; i < positions.length; i++) { positions[i].flag = flags[i]; } const newFileInfo = new QingkuaiFileInfo( params.content, params.isTS, existing?.version ?? 0, path, params.getTypeDelayIndexes, params.identifierStatusInfo, adapter, itos, stoi, positions ); newFileInfo.isOpen = !!existing?.isOpen; newFileInfo.updateContent(params.content); adapter.qingkuaiFileInfos.set(path, newFileInfo); newFileInfo.confirmTypes(); return { aitos: compressNumberArray(itos), astoi: compressNumberArray(stoi) }; } function ensureGetQingkuaiFileInfo(adapter, path) { const existing = adapter.qingkuaiFileInfos.get(path); if (existing) { return existing; } const newFileInfo = compileQingkuaiFile(adapter, path); return newFileInfo.confirmTypes(), newFileInfo; } function filePathToComponentName(adapter, filePath) { let base = adapter.path.base(filePath); base = base.replace(/[^a-zA-Z]*/, ""); base = base.replace(/[^a-zA-Z\d]/g, ""); if (!base) { return "Anonymous"; } return util.kebab2Camel(base, true); } function compileQingkuaiFile(adapter, path) { const compileRes = adapter.compile(path); const existing = adapter.qingkuaiFileInfos.get(path); const newVersion = existing ? existing.version + 1 : 0; const fileInfo = new QingkuaiFileInfo( compileRes.code, compileRes.scriptDescriptor.isTS, newVersion, path, compileRes.getTypeDelayInterIndexes, compileRes.identifierStatusInfo, adapter, compileRes.indexMap.itos, compileRes.indexMap.stoi, compileRes.positions ); return adapter.qingkuaiFileInfos.set(path, fileInfo), fileInfo; } function convertJsDocTagsToMarkdown(tags) { const labels = tags.map((tag) => { switch (tag.name) { case "augments": case "extends": case "param": case "template": { const body = getTagBody(tag); if (body?.length === 3) { const param = body[1]; const doc = body[2]; const label2 = `*@${tag.name}* \`${param}\``; if (!doc) { return label2; } return label2 + (doc.match(/\r\n|\n/g) ? " \n" + doc : ` \u2014 ${doc}`); } break; } case "return": case "returns": { if (!tag.text?.length) { return void 0; } break; } } const label = `*@${tag.name}*`; const text = getTagBodyText(tag); if (!text) { return label; } return label + (text.match(/\r\n|\n/g) ? " \n" + text : ` \u2014 ${text}`); }); return labels.filter((label) => !!label).join(" \n\n"); } function convertDisplayPartsToPlainTextWithLink(parts) { if (isUndefined(parts)) { return ""; } return parts.reduce((ret, part) => { if (part.kind === "linkText") { let spaceIndex = part.text.indexOf(" "); if (spaceIndex === -1) { spaceIndex = part.text.length; } return ret + `[${part.text.slice(spaceIndex)}](${part.text.slice(0, spaceIndex)})`; } else if (part.kind === "linkName") { const target = part.target; const args = encodeURIComponent( JSON.stringify({ end: target.textSpan.start, start: target.textSpan.start, path: ts.server.toNormalizedPath(target.fileName) }) ); return ret + `[${part.text}](command:qingkuai.openFileByFilePath?${args})`; } return (ret + (part.kind === "link" ? "" : part.text || "")).replace(LSU_AND_DOT, ""); }, ""); } function getTagBodyText(tag) { if (!tag.text) { return void 0; } function makeCodeblock(text2) { if (/^\s*[~`]{3}/m.test(text2)) { return text2; } return "```\n" + text2 + "\n```"; } let text = convertDisplayPartsToPlainTextWithLink(tag.text); switch (tag.name) { case "example": { const captionTagMatches = text.match(/<caption>(.*?)<\/caption>\s*(\r\n|\n)/); if (captionTagMatches && captionTagMatches.index === 0) { return captionTagMatches[1] + "\n" + makeCodeblock(text.slice(captionTagMatches[0].length)); } else { return makeCodeblock(text); } } case "author": { const emailMatch = text.match(/(.+)\s<([-.\w]+@[-.\w]+)>/); if (emailMatch === null) { return text; } else { return `${emailMatch[1]} ${emailMatch[2]}`; } } case "default": { return makeCodeblock(text); } default: { return text; } } } function getTagBody(tag) { if (tag.name === "template") { const parts = tag.text; if (parts && typeof parts !== "string") { const docs = parts.filter((p) => p.kind === "text"); const docsText = docs.map((p) => p.text.replace(/^\s*-?\s*/, "")).join(" "); const params = parts.filter((p) => p.kind === "typeParameterName"); const paramsText = params.map((p) => p.text).join(", "); return params ? ["", paramsText, docsText] : void 0; } } return convertDisplayPartsToPlainTextWithLink(tag.text).split(/^(\S+)\s*-?\s*/); } function getAndConvertCompletionInfo(adapter, params) { const filePath = adapter.getNormalizedPath(params.fileName); const languageService = adapter.getDefaultLanguageService(filePath); debugAssert(!!languageService); const completionRes = languageService.getCompletionsAtPosition( filePath, params.pos, { ...adapter.getUserPreferences(filePath), triggerKind: params.triggerKind, includeInsertTextCompletions: true, triggerCharacter: params.triggerCharacter }, adapter.getFormattingOptions(filePath) ); if (!completionRes) { return null; } const convertedEntries = []; for (const entry of completionRes.entries || []) { const kindModifiers = parseKindModifier(entry.kindModifiers); convertedEntries.push({ ...entry, name: entry.name, isColor: kindModifiers.has("color"), detail: getScriptKindDetails(entry), isDeprecated: kindModifiers.has("deprecated"), label: entry.name + (kindModifiers.has("optional") ? "?" : "") }); } return { ...completionRes, entries: convertedEntries }; } function getAndConvertCompletionDetail(adapter, lsParams) { const { fileName, pos, entryName, original, source } = lsParams; const filePath = adapter.getNormalizedPath(fileName); const languageService = adapter.getDefaultLanguageService(filePath); debugAssert(!!languageService); const userPreferences = adapter.getUserPreferences(fileName); const formattingOptions = adapter.getFormattingOptions(fileName); const detail = languageService.getCompletionEntryDetails( fileName, pos, entryName, formattingOptions, source, userPreferences, original ); if (!detail) { return null; } const converted = { codeActions: [], name: detail.name, kind: detail.kind, kindModifiers: detail.kindModifiers, detail: ts.displayPartsToString(detail.displayParts) }; if (detail.tags?.length) { converted.tags = detail.tags; } if (detail.codeActions?.length) { const resolveImportExtension = adapter.getQingkuaiConfig(fileName)?.resolveImportExtension; detail.codeActions?.forEach((action) => { const effects = []; const currentFileChanges = []; if (resolveImportExtension) { action.description = action.description.replace(qkExtInImportRE, "$1"); } action.changes.forEach((change) => { const changeFilePath = adapter.getNormalizedPath(change.fileName); const changeFileInfo = adapter.service.ensureGetQingkuaiFileInfo(changeFilePath); const currentEffect = { textChanges: [], fileName: changeFilePath, isNewFile: change.isNewFile }; change.textChanges.forEach((item) => { const sourceStartIndex = changeFileInfo.getSourceIndex(item.span.start); if (fileName === change.fileName) { const sourceEndIndex = changeFileInfo.getSourceIndex( item.span.start + item.span.length ); const willEditImport = CompletionImportTextEditRE.test(item.newText); if (willEditImport && resolveImportExtension) { item.newText = item.newText.replace(qkExtInImportRE, "$1"); } if (willEditImport || !isIndexesInvalid(sourceStartIndex, sourceEndIndex)) { currentFileChanges.push({ newText: item.newText, range: [sourceStartIndex, sourceEndIndex] }); } } else if (!isIndexesInvalid(sourceStartIndex)) { currentEffect.textChanges.push({ newText: item.newText, span: { start: sourceStartIndex, length: item.span.length } }); } }); if (currentEffect.textChanges.length) { effects.push(currentEffect); } }); converted.codeActions.push({ effects, currentFileChanges, commands: action.commands, description: action.description }); }); } if (detail.documentation) { converted.documentation = convertDisplayPartsToPlainTextWithLink(detail.documentation); } return converted; } function proxyGetCompletionsAtPositionToConvert(adapter, project) { const languageService = project.getLanguageService(); const getCompletionsAtPosition = languageService.getCompletionsAtPosition; languageService.getCompletionsAtPosition = (fileName, position, preference, formattingOptions) => { const originalResult = getCompletionsAtPosition.call( languageService, fileName, position, preference, formattingOptions ); if (originalResult?.entries) { originalResult.entries = originalResult.entries.filter((entry) => { const sourceFileName = entry.data?.fileName; if (sourceFileName && isQingkuaiFileName(sourceFileName)) { entry.name = adapter.service.ensureGetQingkuaiFileInfo(sourceFileName).componentName; } if (entry.name.startsWith(constants.PRESERVED_IDPREFIX)) { return false; } if (INVALID_COMPLETION_TEXT_LABELS.has(entry.name)) { const entryDetail = languageService.getCompletionEntryDetails( fileName, position, entry.name, formattingOptions, entry.source, preference, entry.data ); return !entryDetail?.displayParts.some((part) => { return part.text === constants.LSC.UTIL; }); } return !entry.source || !INVLALID_COMPLETION_PACKAGES.has(entry.source); }); } return originalResult; }; } function proxyGetCompletionEntryDetailsToConvert(_, project) { const languageService = project.getLanguageService(); const getCompletionEntryDetails = languageService.getCompletionEntryDetails; languageService.getCompletionEntryDetails = (...args) => { const originalRet = getCompletionEntryDetails.apply(languageService, args); if (originalRet && args[6]?.fileName && isQingkuaiFileName(args[6].fileName)) { originalRet.tags?.forEach((tag) => { tag.text?.forEach((item) => { item.text = item.text.replaceAll(LSU_AND_DOT, ""); }); }); for (let i = 0; i < originalRet.displayParts.length; i++) { if (originalRet.displayParts[i].text !== constants.LSC.UTIL) { continue; } if (originalRet.displayParts[i + 1].text === ".") { originalRet.displayParts[i + 1].text = ""; } originalRet.displayParts[i].text = ""; } } return originalRet; }; } function parseKindModifier(kindModifiers) { if (isUndefined(kindModifiers)) { kindModifiers = ""; } return new Set(kindModifiers.split(/,|\s+/g)); } function getScriptKindDetails(entry) { if (!entry.kindModifiers || entry.kind !== ts.ScriptElementKind.scriptElement) { return void 0; } const kindModifiers = parseKindModifier(entry.kindModifiers); for (const extModifier of SCRIPT_EXTENSIONS) { if (kindModifiers.has(extModifier)) { if (entry.name.toLowerCase().endsWith(extModifier)) { return entry.name; } else { return entry.name + extModifier; } } } return void 0; } function getAndConvertDefinitions(adapter, params) { const filePath = adapter.getNormalizedPath(params.fileName); const languageService = adapter.getDefaultLanguageService(filePath); if (!debugAssert(languageService)) { return null; } const data = languageService.getDefinitionAndBoundSpan(filePath, params.pos); if (!data?.definitions) { return null; } const locationConvertor = adapter.service.createLocationConvertor(filePath); const dealtDefinitions = data.definitions.map((definition) => { const definitionLocationConvertor = adapter.service.createLocationConvertor( definition.fileName ); const range = definitionLocationConvertor.languageServerRange.fromSourceTextSpan( definition.textSpan ); if (definition.contextSpan) { const contextRange = definition.contextSpan && definitionLocationConvertor.languageServerRange.fromSourceTextSpan( definition.contextSpan ); return { fileName: definition.fileName, targetSelectionRange: range, targetRange: contextRange ?? range }; } return { fileName: definition.fileName, targetSelectionRange: range, targetRange: range }; }); return { definitions: dealtDefinitions, range: locationConvertor.languageServerRange.fromTextSpan(data.textSpan) }; } function getAndConvertTypeDefinitions(adapter, params) { const filePath = adapter.getNormalizedPath(params.fileName); const languageService = adapter.getDefaultLanguageService(filePath); if (!debugAssert(languageService)) { return null; } const definitions = languageService.getTypeDefinitionAtPosition(filePath, params.pos); if (!definitions?.length) { return null; } return definitions.map((definition) => { let contextRange = void 0; const definitionFilePath = adapter.getNormalizedPath(definition.fileName); const definitionLocationConvertor = adapter.service.createLocationConvertor(definitionFilePath); const range = definitionLocationConvertor.languageServerRange.fromTextSpan( definition.textSpan ); if (definition.contextSpan) { contextRange = definitionLocationConvertor.languageServerRange.fromTextSpan( definition.contextSpan ); } return { fileName: filePath, targetRange: range, targetSelectionRange: contextRange ?? range }; }); } function proxyGetDefinitionAndBoundSpanToConvert(adapter, project) { const languageService = project.getLanguageService(); const getDefinitionAndBoundSpan = languageService.getDefinitionAndBoundSpan; languageService.getDefinitionAndBoundSpan = (fileName, position) => { const originalRet = getDefinitionAndBoundSpan.call(languageService, fileName, position); if (!originalRet?.definitions) { return; } originalRet.definitions.forEach((definition) => { if (isQingkuaiFileName(definition.fileName)) { const definitionLocationConvertor = adapter.service.createLocationConvertor( definition.fileName ); definition.textSpan = definitionLocationConvertor.textSpan.toSourceTextSpan( definition.textSpan ); definition.contextSpan = definition.contextSpan && definitionLocationConvertor.textSpan.toSourceTextSpan(definition.contextSpan); } }); return originalRet; }; } function proxyGetTypeDefinitionAtPositionToConvert(adapter, project) { const languageService = project.getLanguageService(); const getTypeDefinitionAtPosition = languageService.getTypeDefinitionAtPosition; languageService.getTypeDefinitionAtPosition = (fileName, position) => { const originalRet = getTypeDefinitionAtPosition.call(languageService, fileName, position); originalRet?.forEach((definition) => { if (isQingkuaiFileName(definition.fileName)) { const definitionLocationConvertor = adapter.service.createLocationConvertor( definition.fileName ); definition.textSpan = definitionLocationConvertor.textSpan.toSourceTextSpan( definition.textSpan ); definition.contextSpan = definition.contextSpan && definitionLocationConvertor.textSpan.toSourceTextSpan(definition.contextSpan); } }); return originalRet; }; } function getAndConvertReferences(adapter, params) { const result = []; const filePath = adapter.getNormalizedPath(params.fileName); const languageService = adapter.getDefaultLanguageService(filePath); if (!debugAssert(languageService)) { return null; } const data = languageService.findReferences(filePath, params.pos); data?.forEach((item) => { item.references.forEach((reference) => { if (reference.isDefinition) { return; } const referenceLocationConvertor = adapter.service.createLocationConvertor( reference.fileName ); result.push({ range: referenceLocationConvertor.languageServerRange.fromSourceTextSpan( reference.textSpan ), fileName: adapter.getNormalizedPath(reference.fileName) }); }); }); return result; } function proxyFindReferencesToConvert(adapter, project) { const languageService = project.getLanguageService(); const findReferences = languageService.findReferences; languageService.findReferences = (fileName, position) => { const originalRet = findReferences.call(languageService, fileName, position); originalRet?.forEach((item) => { item.references = item.references.filter((reference) => { if (!isQingkuaiFileName(reference.fileName)) { return true; } reference.textSpan.start; const referenceLocationConvertor = adapter.service.createLocationConvertor( reference.fileName ); reference.textSpan = referenceLocationConvertor.textSpan.toSourceTextSpan( reference.textSpan ); reference.contextSpan = reference.contextSpan && referenceLocationConvertor.textSpan.toSourceTextSpan(reference.contextSpan); return reference.textSpan !== referenceLocationConvertor.textSpan.defaultValue; }); }); return originalRet; }; } function findAndConvertImplementations(adapter, params) { const filePath = adapter.getNormalizedPath(params.fileName); const languageService = adapter.getDefaultLanguageService(filePath); if (!debugAssert(languageService)) { return null; } const implementations = languageService.getImplementationAtPosition(filePath, params.pos); if (!implementations) { return null; } return implementations.map((implementation) => { const implementationLocationConvertor = adapter.service.createLocationConvertor( implementation.fileName ); return { range: implementationLocationConvertor.languageServerRange.fromSourceTextSpan( implementation.textSpan ), fileName: adapter.getNormalizedPath(implementation.fileName) }; }); } function proxyGetImplementationAtPositionToConvert(adapter, project) { const languageService = project.getLanguageService(); const getImplementationsAtPosition = languageService.getImplementationAtPosition; languageService.getImplementationAtPosition = (fileName, position) => { const originalRet = getImplementationsAtPosition.call(languageService, fileName, position); return originalRet?.filter((implementation) => { if (!isQingkuaiFileName(implementation.fileName)) { return true; } implementation.textSpan.start; const implementationLocationConvertor = adapter.service.createLocationConvertor( implementation.fileName ); implementation.contextSpan = implementation.contextSpan && implementationLocationConvertor.textSpan.toSourceTextSpan( implementation.contextSpan ); implementation.textSpan = implementationLocationConvertor.textSpan.toSourceTextSpan( implementation.textSpan ); return implementation.textSpan !== implementationLocationConvertor.textSpan.defaultValue; }); }; } function getAndConvertHoverTip(adapter, { fileName, pos }) { const fileInfo = adapter.service.ensureGetQingkuaiFileInfo(fileName); const program = adapter.getDefaultProgram(fileInfo.path); if (!debugAssert(program)) { return null; } let idStatusDisplay = ""; const typeChecker = program.getTypeChecker(); const config = adapter.getQingkuaiConfig(fileInfo.path); const languageService = adapter.getDefaultLanguageService(fileInfo.path); const node = getNodeAtPositionAndWithin(program.getSourceFile(fileName), pos); if (node && ts.isIdentifier(node)) { const nodeRange = [node.getStart(), node.getEnd()]; if (config?.hoverTipReactiveStatus && fileInfo.idStatusInfo[node.text]) { const symbol = typeChecker.getSymbolAtLocation(node); if (symbol && symbol.declarations && !(symbol.flags & ts.SymbolFlags.Alias) && symbol.declarations.some((decl) => isInTopScope(decl))) { const statusInfo = fileInfo.idStatusInfo[node.text]; idStatusDisplay = ` // - ${statusInfo} -`; } } if (node.text.startsWith(constants.PRESERVED_IDPREFIX)) { return { content: "any", range: nodeRange }; } if (node.parent && ts.isPropertyAccessExpression(node.parent) && node.parent.expression.getText() === constants.LSC.UTIL) { return { content: "any", range: nodeRange }; } } const ret = languageService.getQuickInfoAtPosition(fileName, pos); if (isUndefined(ret)) { return null; } const { start, length } = ret.textSpan; const documentation = convertDisplayPartsToPlainTextWithLink(ret.documentation); const display = convertDisplayPartsToPlainTextWithLink(ret.displayParts).replace( LSU_AND_DOT, "" ); return { range: [start, start + length], content: mdCodeBlockGen("ts", display + idStatusDisplay) + "\n" + documentation }; } function proxyGetQuickInfoAtPosition(_, project) { const languageService = project.getLanguageService(); const getQuickInfoAtPosition = languageService.getQuickInfoAtPosition; languageService.getQuickInfoAtPosition = (fileName, position) => { const originalRet = getQuickInfoAtPosition.call(languageService, fileName, position); if (originalRet?.displayParts) { originalRet.displayParts = filterDisplayParts(originalRet.displayParts); } if (originalRet?.documentation) { originalRet.documentation = filterDisplayParts(originalRet.documentation); } return originalRet; }; } function filterDisplayParts(target) { return target?.filter((item, index) => { switch (item.text) { case constants.LSC.UTIL: case constants.LSC.COMPONENT: case constants.PRESERVED_IDPREFIX: case constants.LSC.GET_TYPE_DELAY_MARKING: { return false; } case ".": { return target[index - 1].text !== constants.LSC.UTIL; } default: { return true; } } }); } function proxyProject(adapter, project) { const projectAny = project; if (!projectAny[PROXIED_MARK]) { projectAny[PROXIED_MARK] = true; for (const proxyFn of [ proxyGetScriptVersion, proxyGetScriptKind, proxyGetScriptSnapshot, proxyGetQuickInfoAtPosition, proxyFindReferencesToConvert, proxyResolveModuleNameLiterals, proxyGetCompletionsAtPositionToConvert, proxyGetCompletionEntryDetailsToConvert, proxyGetDefinitionAndBoundSpanToConvert, proxyGetTypeDefinitionAtPositionToConvert, proxyGetImplementationAtPositionToConvert ]) { proxyFn(adapter, project); } } } function proxyGetScriptSnapshot(adapter, languageServiceHost) { const getScriptSnapshot = languageServiceHost.getScriptSnapshot; languageServiceHost.getScriptSnapshot = (fileName) => { const originalRet = getScriptSnapshot?.call(languageServiceHost, fileName); if (!isQingkuaiFileName(fileName)) { return originalRet; } return adapter.ts.ScriptSnapshot.fromString( adapter.service.ensureGetQingkuaiFileInfo(fileName).code ); }; } function proxyGetScriptVersion(adapter, languageServiceHost) { const getScriptVersion = languageServiceHost.getScriptVersion; languageServiceHost.getScriptVersion = (fileName) => { if (!isQingkuaiFileName(fileName)) { return getScriptVersion?.call(languageServiceHost, fileName) ?? ""; } return adapter.service.ensureGetQingkuaiFileInfo(fileName).version.toString(); }; } function proxyGetScriptKind(adapter, languageServiceHost) { const getScriptKind = languageServiceHost.getScriptKind; if (getScriptKind) { languageServiceHost.getScriptKind = (fileName) => { if (!isQingkuaiFileName(fileName)) { return getScriptKind.call(languageServiceHost, fileName); } return adapter.service.ensureGetQingkuaiFileInfo(fileName).isTS ? adapter.ts.ScriptKind.TS : adapter.ts.ScriptKind.JS; }; } } function proxyResolveModuleNameLiterals(adapter, languageServiceHost) { const resolveModuleLiterals = languageServiceHost.resolveModuleNameLiterals; if (isUndefined(resolveModuleLiterals)) { return; } languageServiceHost.resolveModuleNameLiterals = (moduleLiterals, containingFile, ...rest) => { const originalRet = resolveModuleLiterals.call( languageServiceHost, moduleLiterals, containingFile, ...rest ); const containingFileInfo = isQingkuaiFileName(containingFile) ? adapter.service.ensureGetQingkuaiFileInfo(containingFile) : void 0; const importByQingkuaiFile = isQingkuaiFileName(containingFile); const containingFilePath = adapter.getNormalizedPath(containingFile); const qingkuaiConfig = adapter.getQingkuaiConfig(containingFilePath); const dirPath = adapter.getNormalizedPath(adapter.path.dir(containingFilePath)); const importedQingkuaiModuleTexts = /* @__PURE__ */ new Set(); adapter.resolvedQingkuaiModules.set(containingFilePath, importedQingkuaiModuleTexts); const ret = originalRet.map((item, index) => { const moduleText = moduleLiterals[index].text; const isDirectory = isEmptyString(adapter.path.ext(moduleText)); const inferredAsQingkuaiFile = importByQingkuaiFile && isDirectory && qingkuaiConfig?.resolveImportExtension; const modulePath = adapter.getNormalizedPath( adapter.path.resolve(dirPath, moduleText + (inferredAsQingkuaiFile ? ".qk" : "")) ); const moduleFileInfo = isQingkuaiFileName(modulePath) && adapter.fs.exist(modulePath) ? adapter.service.ensureGetQingkuaiFileInfo(modulePath) : void 0; if (!moduleFileInfo || modulePath === containingFileInfo?.path) { if (inferredAsQingkuaiFile && !item.resolvedModule) { (item.failedLookupLocations ?? (item.failedLookupLocations = [])).push(modulePath); } return item; } importedQingkuaiModuleTexts.add(moduleText); return { ...item, resolvedModule: { isExternalLibraryImport: false, resolvedUsingTsExtension: false, extension: moduleFileInfo.isTS ? ".ts" : ".js", resolvedFileName: adapter.getNormalizedPath(modulePath) }, failedLookupLocations: void 0 }; }); return ret; }; } function getNavigationTree(adapter, fileName) { const filePath = adapter.getNormalizedPath(fileName); const languageService = adapter.getDefaultLanguageService(filePath); if (!languageService) { return null; } return languageService.getNavigationTree(filePath); } function getComponentInfos(adapter, filePath) { const sourceFile = adapter?.getDefaultSourceFile(filePath); if (!debugAssert(sourceFile)) { return []; } const dirPath = adapter.path.dir(filePath); const config = adapter.getQingkuaiConfig(filePath); const importedQingkuaiFileNames = /* @__PURE__ */ new Set(); const componentInfos = []; const qingkuaiModules = adapter.resolvedQingkuaiModules.get(filePath); walkTsNode(sourceFile, (node) => { if (adapter.ts.isImportDeclaration(node) && isInTopScope(node)) { if (!isUndefined(node.importClause?.name)) { const identifierName = node.importClause.name.text; if (adapter.ts.isStringLiteral(node.moduleSpecifier) && qingkuaiModules?.has(node.moduleSpecifier.text)) { let relative = adapter.getNormalizedPath(node.moduleSpecifier.text); let absolute = adapter.path.resolve(dirPath, node.moduleSpecifier.text); const extension = !isQingkuaiFileName(relative) ? ".qk" : ""; const targetFileInfo = adapter.service.ensureGetQingkuaiFileInfo( absolute + extension ); componentInfos.push({ imported: true, name: identifierName, absolutePath: targetFileInfo.path, relativePath: relative + extension, slotNames: targetFileInfo.slotNames