"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.VueFile = exports.VueEmbeddedFile = void 0; const language_core_1 = require("@volar/language-core"); const source_map_1 = require("@volar/source-map"); const reactivity_1 = require("@vue/reactivity"); const muggle = require("muggle-string"); class VueEmbeddedFile { constructor(fileName) { this.fileName = fileName; this.kind = language_core_1.FileKind.TextFile; this.capabilities = {}; this.content = []; this.mirrorBehaviorMappings = []; } } exports.VueEmbeddedFile = VueEmbeddedFile; class VueFile { get compiledSFCTemplate() { return this._compiledSfcTemplate.value; } get mainScriptName() { return this._allEmbeddedFiles.value.find(e => e.file.fileName.replace(this.fileName, '').match(/^\.(js|ts)x?$/))?.file.fileName ?? ''; } get embeddedFiles() { return this._embeddedFiles.value; } // functions constructor(fileName, snapshot, ts, plugins) { this.fileName = fileName; this.snapshot = snapshot; this.ts = ts; this.plugins = plugins; this.capabilities = language_core_1.FileCapabilities.full; this.kind = language_core_1.FileKind.TextFile; this.mappings = []; // refs this.sfc = (0, reactivity_1.reactive)({ template: null, script: null, scriptSetup: null, styles: [], customBlocks: [], templateAst: (0, reactivity_1.computed)(() => { return this._compiledSfcTemplate.value?.ast; }), scriptAst: (0, reactivity_1.computed)(() => { if (this.sfc.script) { return this.ts.createSourceFile(this.fileName + '.' + this.sfc.script.lang, this.sfc.script.content, this.ts.ScriptTarget.Latest); } }), scriptSetupAst: (0, reactivity_1.computed)(() => { if (this.sfc.scriptSetup) { return this.ts.createSourceFile(this.fileName + '.' + this.sfc.scriptSetup.lang, this.sfc.scriptSetup.content, this.ts.ScriptTarget.Latest); } }), ...{ // backward compatible getTemplateAst: () => { return this.compiledSFCTemplate?.ast; }, }, }) /* avoid Sfc unwrap in .d.ts by reactive */; // computed this._sfcBlocks = (0, reactivity_1.computed)(() => { const blocks = {}; if (this.sfc.template) { blocks[this.sfc.template.name] = this.sfc.template; } if (this.sfc.script) { blocks[this.sfc.script.name] = this.sfc.script; } if (this.sfc.scriptSetup) { blocks[this.sfc.scriptSetup.name] = this.sfc.scriptSetup; } for (const block of this.sfc.styles) { blocks[block.name] = block; } for (const block of this.sfc.customBlocks) { blocks[block.name] = block; } return blocks; }); this._compiledSfcTemplate = (0, reactivity_1.computed)(() => { if (this.compiledSFCTemplateCache?.template === this.sfc.template?.content) { return { errors: [], warnings: [], ast: this.compiledSFCTemplateCache?.result.ast, }; } if (this.sfc.template) { // incremental update if (this.compiledSFCTemplateCache?.plugin.updateSFCTemplate) { const change = this.snapshot.getChangeRange(this.compiledSFCTemplateCache.snapshot); if (change) { (0, reactivity_1.pauseTracking)(); const templateOffset = this.sfc.template.startTagEnd; (0, reactivity_1.resetTracking)(); const newText = this.snapshot.getText(change.span.start, change.span.start + change.newLength); const newResult = this.compiledSFCTemplateCache.plugin.updateSFCTemplate(this.compiledSFCTemplateCache.result, { start: change.span.start - templateOffset, end: change.span.start + change.span.length - templateOffset, newText, }); if (newResult) { this.compiledSFCTemplateCache.template = this.sfc.template.content; this.compiledSFCTemplateCache.snapshot = this.snapshot; this.compiledSFCTemplateCache.result = newResult; return { errors: [], warnings: [], ast: newResult.ast, }; } } } const errors = []; const warnings = []; let options = { onError: (err) => errors.push(err), onWarn: (err) => warnings.push(err), expressionPlugins: ['typescript'], }; for (const plugin of this.plugins) { if (plugin.resolveTemplateCompilerOptions) { options = plugin.resolveTemplateCompilerOptions(options); } } for (const plugin of this.plugins) { let result; try { result = plugin.compileSFCTemplate?.(this.sfc.template.lang, this.sfc.template.content, options); } catch (e) { const err = e; errors.push(err); } if (result || errors.length) { if (result && !errors.length && !warnings.length) { this.compiledSFCTemplateCache = { template: this.sfc.template.content, snapshot: this.snapshot, result: result, plugin, }; } else { this.compiledSFCTemplateCache = undefined; } return { errors, warnings, ast: result?.ast, }; } } } }); this._pluginEmbeddedFiles = this.plugins.map((plugin) => { const embeddedFiles = {}; const files = (0, reactivity_1.computed)(() => { try { if (plugin.getEmbeddedFileNames) { const embeddedFileNames = plugin.getEmbeddedFileNames(this.fileName, this.sfc); for (const oldFileName of Object.keys(embeddedFiles)) { if (!embeddedFileNames.includes(oldFileName)) { delete embeddedFiles[oldFileName]; } } for (const embeddedFileName of embeddedFileNames) { if (!embeddedFiles[embeddedFileName]) { embeddedFiles[embeddedFileName] = (0, reactivity_1.computed)(() => { const file = new VueEmbeddedFile(embeddedFileName); for (const plugin of this.plugins) { if (plugin.resolveEmbeddedFile) { try { plugin.resolveEmbeddedFile(this.fileName, this.sfc, file); } catch (e) { console.error(e); } } } return file; }); } } } } catch (e) { console.error(e); } return Object.values(embeddedFiles); }); return (0, reactivity_1.computed)(() => { return files.value.map(_file => { const file = _file.value; const mappings = (0, source_map_1.buildMappings)(file.content); for (const mapping of mappings) { if (mapping.source !== undefined) { const block = this._sfcBlocks.value[mapping.source]; if (block) { mapping.sourceRange = [ mapping.sourceRange[0] + block.startTagEnd, mapping.sourceRange[1] + block.startTagEnd, ]; mapping.source = undefined; } } } const newText = (0, source_map_1.toString)(file.content); const changeRanges = new Map(); const snapshot = { getText: (start, end) => newText.slice(start, end), getLength: () => newText.length, getChangeRange(oldSnapshot) { if (!changeRanges.has(oldSnapshot)) { changeRanges.set(oldSnapshot, undefined); const oldText = oldSnapshot.getText(0, oldSnapshot.getLength()); for (let start = 0; start < oldText.length && start < newText.length; start++) { if (oldText[start] !== newText[start]) { let end = oldText.length; for (let i = 0; i < oldText.length - start && i < newText.length - start; i++) { if (oldText[oldText.length - i - 1] !== newText[newText.length - i - 1]) { break; } end--; } let length = end - start; let newLength = length + (newText.length - oldText.length); if (newLength < 0) { length -= newLength; newLength = 0; } changeRanges.set(oldSnapshot, { span: { start, length }, newLength, }); break; } } } return changeRanges.get(oldSnapshot); }, }; return { file, snapshot, mappings, }; }); }); }); this._allEmbeddedFiles = (0, reactivity_1.computed)(() => { const all = []; for (const embeddedFiles of this._pluginEmbeddedFiles) { for (const embedded of embeddedFiles.value) { all.push(embedded); } } return all; }); this._embeddedFiles = (0, reactivity_1.computed)(() => { const embeddedFiles = []; let remain = [...this._allEmbeddedFiles.value]; while (remain.length) { const beforeLength = remain.length; consumeRemain(); if (beforeLength === remain.length) { break; } } for (const { file, snapshot, mappings } of remain) { embeddedFiles.push({ ...file, snapshot, mappings, embeddedFiles: [], }); console.error('Unable to resolve embedded: ' + file.parentFileName + ' -> ' + file.fileName); } return embeddedFiles; function consumeRemain() { for (let i = remain.length - 1; i >= 0; i--) { const { file, snapshot, mappings } = remain[i]; if (!file.parentFileName) { embeddedFiles.push({ ...file, snapshot, mappings, embeddedFiles: [], }); remain.splice(i, 1); } else { const parent = findParentStructure(file.parentFileName, embeddedFiles); if (parent) { parent.embeddedFiles.push({ ...file, snapshot, mappings, embeddedFiles: [], }); remain.splice(i, 1); } } } } function findParentStructure(fileName, current) { for (const child of current) { if (child.fileName === fileName) { return child; } let parent = findParentStructure(fileName, child.embeddedFiles); if (parent) { return parent; } } } }); this.update(snapshot); } update(newScriptSnapshot) { this.snapshot = newScriptSnapshot; this.parsedSfc = this.parseSfc(); if (this.parsedSfc) { this.updateTemplate(this.parsedSfc.descriptor.template); this.updateScript(this.parsedSfc.descriptor.script); this.updateScriptSetup(this.parsedSfc.descriptor.scriptSetup); this.updateStyles(this.parsedSfc.descriptor.styles); this.updateCustomBlocks(this.parsedSfc.descriptor.customBlocks); } else { this.updateTemplate(null); this.updateScript(null); this.updateScriptSetup(null); this.updateStyles([]); this.updateCustomBlocks([]); } const str = [[this.snapshot.getText(0, this.snapshot.getLength()), undefined, 0, language_core_1.FileRangeCapabilities.full]]; for (const block of [ this.sfc.script, this.sfc.scriptSetup, this.sfc.template, ...this.sfc.styles, ...this.sfc.customBlocks, ]) { if (block) { muggle.replaceSourceRange(str, undefined, block.startTagEnd, block.endTagStart, [ block.content, undefined, block.startTagEnd, block.name === 'template' ? { completion: true } // fix vue-autoinsert-parentheses not working : {}, ]); } } this.mappings = str.map((m) => { const text = m[0]; const start = m[2]; const end = start + text.length; return { sourceRange: [start, end], generatedRange: [start, end], data: m[3], }; }); } parseSfc() { // incremental update if (this.parsedSfcCache?.plugin.updateSFC) { const change = this.snapshot.getChangeRange(this.parsedSfcCache.snapshot); if (change) { const newSfc = this.parsedSfcCache.plugin.updateSFC(this.parsedSfcCache.sfc, { start: change.span.start, end: change.span.start + change.span.length, newText: this.snapshot.getText(change.span.start, change.span.start + change.newLength), }); if (newSfc) { this.parsedSfcCache.snapshot = this.snapshot; this.parsedSfcCache.sfc = newSfc; return newSfc; } } } for (const plugin of this.plugins) { const sfc = plugin.parseSFC?.(this.fileName, this.snapshot.getText(0, this.snapshot.getLength())); if (sfc) { if (!sfc.errors.length) { this.parsedSfcCache = { snapshot: this.snapshot, sfc, plugin, }; } return sfc; } } } updateTemplate(block) { const newData = block ? { name: 'template', start: this.snapshot.getText(0, block.loc.start.offset).lastIndexOf('<' + block.type), end: block.loc.end.offset + this.snapshot.getText(block.loc.end.offset, this.snapshot.getLength()).indexOf('>') + 1, startTagEnd: block.loc.start.offset, endTagStart: block.loc.end.offset, content: block.content, lang: block.lang ?? 'html', attrs: block.attrs, } : null; if (this.sfc.template && newData) { this.updateBlock(this.sfc.template, newData); } else { this.sfc.template = newData; } } updateScript(block) { const newData = block ? { name: 'script', start: this.snapshot.getText(0, block.loc.start.offset).lastIndexOf('<' + block.type), end: block.loc.end.offset + this.snapshot.getText(block.loc.end.offset, this.snapshot.getLength()).indexOf('>') + 1, startTagEnd: block.loc.start.offset, endTagStart: block.loc.end.offset, content: block.content, lang: block.lang ?? 'js', src: block.src, srcOffset: block.src ? this.snapshot.getText(0, block.loc.start.offset).lastIndexOf(block.src) - block.loc.start.offset : -1, attrs: block.attrs, } : null; if (this.sfc.script && newData) { this.updateBlock(this.sfc.script, newData); } else { this.sfc.script = newData; } } updateScriptSetup(block) { const newData = block ? { name: 'scriptSetup', start: this.snapshot.getText(0, block.loc.start.offset).lastIndexOf('<' + block.type), end: block.loc.end.offset + this.snapshot.getText(block.loc.end.offset, this.snapshot.getLength()).indexOf('>') + 1, startTagEnd: block.loc.start.offset, endTagStart: block.loc.end.offset, content: block.content, lang: block.lang ?? 'js', generic: typeof block.attrs.generic === 'string' ? block.attrs.generic : undefined, genericOffset: typeof block.attrs.generic === 'string' ? this.snapshot.getText(0, block.loc.start.offset).lastIndexOf(block.attrs.generic) - block.loc.start.offset : -1, attrs: block.attrs, } : null; if (this.sfc.scriptSetup && newData) { this.updateBlock(this.sfc.scriptSetup, newData); } else { this.sfc.scriptSetup = newData; } } updateStyles(blocks) { for (let i = 0; i < blocks.length; i++) { const block = blocks[i]; const newData = { name: 'style_' + i, start: this.snapshot.getText(0, block.loc.start.offset).lastIndexOf('<' + block.type), end: block.loc.end.offset + this.snapshot.getText(block.loc.end.offset, this.snapshot.getLength()).indexOf('>') + 1, startTagEnd: block.loc.start.offset, endTagStart: block.loc.end.offset, content: block.content, lang: block.lang ?? 'css', module: typeof block.module === 'string' ? block.module : block.module ? '$style' : undefined, scoped: !!block.scoped, attrs: block.attrs, }; if (this.sfc.styles.length > i) { this.updateBlock(this.sfc.styles[i], newData); } else { this.sfc.styles.push(newData); } } while (this.sfc.styles.length > blocks.length) { this.sfc.styles.pop(); } } updateCustomBlocks(blocks) { for (let i = 0; i < blocks.length; i++) { const block = blocks[i]; const newData = { name: 'customBlock_' + i, start: this.snapshot.getText(0, block.loc.start.offset).lastIndexOf('<' + block.type), end: block.loc.end.offset + this.snapshot.getText(block.loc.end.offset, this.snapshot.getLength()).indexOf('>') + 1, startTagEnd: block.loc.start.offset, endTagStart: block.loc.end.offset, content: block.content, lang: block.lang ?? 'txt', type: block.type, attrs: block.attrs, }; if (this.sfc.customBlocks.length > i) { this.updateBlock(this.sfc.customBlocks[i], newData); } else { this.sfc.customBlocks.push(newData); } } while (this.sfc.customBlocks.length > blocks.length) { this.sfc.customBlocks.pop(); } } updateBlock(oldBlock, newBlock) { for (const key in newBlock) { if (typeof oldBlock[key] === 'object' && typeof newBlock[key] === 'object') { this.updateBlock(oldBlock[key], newBlock[key]); } else { oldBlock[key] = newBlock[key]; } } for (const key in oldBlock) { if (!(key in newBlock)) { delete oldBlock[key]; } } } } exports.VueFile = VueFile; //# sourceMappingURL=sourceFile.js.map