diff --git a/packages/language-core/lib/generators/inlineCss.ts b/packages/language-core/lib/generators/inlineCss.ts new file mode 100644 index 0000000000..57b6bc7d06 --- /dev/null +++ b/packages/language-core/lib/generators/inlineCss.ts @@ -0,0 +1,36 @@ +import * as CompilerDOM from '@vue/compiler-dom'; +import { forEachElementNode } from './template'; +import { enableAllFeatures } from './utils'; +import type { Code } from '../types'; + +export function* generate(templateAst: NonNullable): Generator { + for (const node of forEachElementNode(templateAst)) { + for (const prop of node.props) { + if ( + prop.type === CompilerDOM.NodeTypes.DIRECTIVE + && prop.name === 'bind' + && prop.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION + && prop.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION + && prop.arg.content === 'style' + && prop.exp.constType === CompilerDOM.ConstantTypes.CAN_STRINGIFY + ) { + const endCrt = prop.arg.loc.source[prop.arg.loc.source.length - 1]; // " | ' + const start = prop.arg.loc.source.indexOf(endCrt) + 1; + const end = prop.arg.loc.source.lastIndexOf(endCrt); + const content = prop.arg.loc.source.substring(start, end); + + yield `x { `; + yield [ + content, + 'template', + prop.arg.loc.start.offset + start, + enableAllFeatures({ + format: false, + structure: false, + }), + ]; + yield ` }\n`; + } + } + } +} diff --git a/packages/language-core/lib/generators/template.ts b/packages/language-core/lib/generators/template.ts index 4f853f93d5..abd4d63930 100644 --- a/packages/language-core/lib/generators/template.ts +++ b/packages/language-core/lib/generators/template.ts @@ -63,7 +63,10 @@ const transformContext: CompilerDOM.TransformContext = { expressionPlugins: ['typescript'], }; -type _CodeAndStack = [codeType: 'ts' | 'tsFormat' | 'inlineCss', ...codeAndStack: CodeAndStack]; +export type _CodeAndStack = [ + codeType: 'ts' | 'tsFormat', + ...codeAndStack: CodeAndStack, +]; export function* generate( ts: typeof import('typescript'), @@ -111,9 +114,6 @@ export function* generate( const _tsFormat = codegenStack ? (code: Code): _CodeAndStack => ['tsFormat', code, getStack()] : (code: Code): _CodeAndStack => ['tsFormat', code, '']; - const _inlineCss = codegenStack - ? (code: Code): _CodeAndStack => ['inlineCss', code, getStack()] - : (code: Code): _CodeAndStack => ['inlineCss', code, '']; const nativeTags = new Set(vueCompilerOptions.nativeTags); const slots = new Map prop.type === CompilerDOM.NodeTypes.DIRECTIVE && (prop.name === 'scope' || prop.name === 'data')); let inScope = false; let originalConditionsNum = blockConditions.length; @@ -1422,36 +1420,6 @@ export function* generate( } } - function* generateInlineCss(props: CompilerDOM.ElementNode['props']): Generator<_CodeAndStack> { - for (const prop of props) { - if ( - prop.type === CompilerDOM.NodeTypes.DIRECTIVE - && prop.name === 'bind' - && prop.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION - && prop.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION - && prop.arg.content === 'style' - && prop.exp.constType === CompilerDOM.ConstantTypes.CAN_STRINGIFY - ) { - const endCrt = prop.arg.loc.source[prop.arg.loc.source.length - 1]; // " | ' - const start = prop.arg.loc.source.indexOf(endCrt) + 1; - const end = prop.arg.loc.source.lastIndexOf(endCrt); - const content = prop.arg.loc.source.substring(start, end); - - yield _inlineCss(`x { `); - yield _inlineCss([ - content, - 'template', - prop.arg.loc.start.offset + start, - enableAllFeatures({ - format: false, - structure: false, - }), - ]); - yield _inlineCss(` }\n`); - } - } - } - function* generateDirectives(node: CompilerDOM.ElementNode): Generator<_CodeAndStack> { for (const prop of node.props) { if ( @@ -1986,21 +1954,21 @@ function getPossibleOriginalComponentNames(tagText: string) { ])]; } -export function* eachElementNode(node: CompilerDOM.RootNode | CompilerDOM.TemplateChildNode): Generator { +export function* forEachElementNode(node: CompilerDOM.RootNode | CompilerDOM.TemplateChildNode): Generator { if (node.type === CompilerDOM.NodeTypes.ROOT) { for (const child of node.children) { - yield* eachElementNode(child); + yield* forEachElementNode(child); } } else if (node.type === CompilerDOM.NodeTypes.ELEMENT) { const patchForNode = getVForNode(node); if (patchForNode) { - yield* eachElementNode(patchForNode); + yield* forEachElementNode(patchForNode); } else { yield node; for (const child of node.children) { - yield* eachElementNode(child); + yield* forEachElementNode(child); } } } @@ -2009,14 +1977,14 @@ export function* eachElementNode(node: CompilerDOM.RootNode | CompilerDOM.Templa for (let i = 0; i < node.branches.length; i++) { const branch = node.branches[i]; for (const childNode of branch.children) { - yield* eachElementNode(childNode); + yield* forEachElementNode(childNode); } } } else if (node.type === CompilerDOM.NodeTypes.FOR) { // v-for for (const child of node.children) { - yield* eachElementNode(child); + yield* forEachElementNode(child); } } } diff --git a/packages/language-core/lib/plugins.ts b/packages/language-core/lib/plugins.ts index 8bfc741375..ceef6cca70 100644 --- a/packages/language-core/lib/plugins.ts +++ b/packages/language-core/lib/plugins.ts @@ -5,7 +5,8 @@ import useVueSfcCustomBlocks from './plugins/vue-sfc-customblocks'; import useVueSfcScriptsFormat from './plugins/vue-sfc-scripts'; import useVueSfcStyles from './plugins/vue-sfc-styles'; import useVueSfcTemplate from './plugins/vue-sfc-template'; -import useHtmlTemplatePlugin from './plugins/vue-template-html'; +import useVueTemplateHtmlPlugin from './plugins/vue-template-html'; +import useVueTemplateInlineCssPlugin from './plugins/vue-template-inline-css'; import useVueTsx from './plugins/vue-tsx'; import { pluginVersion, type VueLanguagePlugin } from './types'; @@ -15,7 +16,8 @@ export function getDefaultVueLanguagePlugins(pluginContext: Parameters { + + return { + + version: 2, + + getEmbeddedCodes(_fileName, sfc) { + if (sfc.template?.ast) { + return [{ id: 'template_inline_css', lang: 'css' }]; + } + return []; + }, + + resolveEmbeddedCode(_fileName, sfc, embeddedFile) { + if (embeddedFile.id === 'template_inline_css') { + embeddedFile.parentCodeId = 'template'; + if (sfc.template?.ast) { + embeddedFile.content.push(...generateInlineCss(sfc.template.ast)); + } + } + }, + }; +}; + +export default plugin; diff --git a/packages/language-core/lib/plugins/vue-tsx.ts b/packages/language-core/lib/plugins/vue-tsx.ts index 8babdc34bf..ad215befb9 100644 --- a/packages/language-core/lib/plugins/vue-tsx.ts +++ b/packages/language-core/lib/plugins/vue-tsx.ts @@ -1,4 +1,4 @@ -import { CodeInformation, Mapping, Segment, StackNode, track } from '@volar/language-core'; +import { Mapping, StackNode, track } from '@volar/language-core'; import { computed, computedSet } from 'computeds'; import { generate as generateScript } from '../generators/script'; import { generate as generateTemplate } from '../generators/template'; @@ -34,7 +34,6 @@ const plugin: VueLanguagePlugin = ctx => { if (sfc.template) { files.push({ id: 'template_format', lang: 'ts' }); - files.push({ id: 'template_style', lang: 'css' }); } return files; @@ -86,19 +85,6 @@ const plugin: VueLanguagePlugin = ctx => { } } } - else if (embeddedFile.id === 'template_style') { - - embeddedFile.parentCodeId = 'template'; - - const template = _tsx.generatedTemplate(); - if (template) { - const [content, contentStacks] = ctx.codegenStack - ? track([...template.cssCodes], template.cssCodeStacks.map(stack => ({ stack, length: 1 }))) - : [[...template.cssCodes], template.cssCodeStacks.map(stack => ({ stack, length: 1 }))]; - embeddedFile.content = content as Segment[]; - embeddedFile.contentStacks = contentStacks; - } - } }, }; @@ -168,7 +154,6 @@ function createTsx( const tsCodes: Code[] = []; const tsFormatCodes: Code[] = []; - const inlineCssCodes: Code[] = []; const tsCodegenStacks: string[] = []; const tsFormatCodegenStacks: string[] = []; const inlineCssCodegenStacks: string[] = []; @@ -195,9 +180,6 @@ function createTsx( else if (type === 'tsFormat') { tsFormatCodes.push(code); } - else if (type === 'inlineCss') { - inlineCssCodes.push(code); - } if (ctx.codegenStack) { if (type === 'ts') { tsCodegenStacks.push(stack); @@ -205,9 +187,6 @@ function createTsx( else if (type === 'tsFormat') { tsFormatCodegenStacks.push(stack); } - else if (type === 'inlineCss') { - inlineCssCodegenStacks.push(stack); - } } current = codegen.next(); } @@ -218,7 +197,6 @@ function createTsx( codeStacks: tsCodegenStacks, formatCodes: tsFormatCodes, formatCodeStacks: tsFormatCodegenStacks, - cssCodes: inlineCssCodes, cssCodeStacks: inlineCssCodegenStacks, }; }); diff --git a/packages/language-service/lib/ideFeatures/nameCasing.ts b/packages/language-service/lib/ideFeatures/nameCasing.ts index cc9dc3af0e..68785bc80d 100644 --- a/packages/language-service/lib/ideFeatures/nameCasing.ts +++ b/packages/language-service/lib/ideFeatures/nameCasing.ts @@ -231,7 +231,7 @@ function getTemplateTagsAndAttrs(sourceFile: VirtualCode): Tags { const ast = sourceFile.sfc.template?.ast; const tags: Tags = new Map(); if (ast) { - for (const node of vue.eachElementNode(ast)) { + for (const node of vue.forEachElementNode(ast)) { if (!tags.has(node.tag)) { tags.set(node.tag, { offsets: [], attrs: new Map() }); diff --git a/packages/language-service/lib/plugins/vue-toggle-v-bind-codeaction.ts b/packages/language-service/lib/plugins/vue-toggle-v-bind-codeaction.ts index bfeefc4261..961ebe4fec 100644 --- a/packages/language-service/lib/plugins/vue-toggle-v-bind-codeaction.ts +++ b/packages/language-service/lib/plugins/vue-toggle-v-bind-codeaction.ts @@ -1,5 +1,5 @@ import type { LanguageServicePlugin, LanguageServicePluginInstance } from '@volar/language-service'; -import { VueGeneratedCode, eachElementNode, type CompilerDOM } from '@vue/language-core'; +import { VueGeneratedCode, forEachElementNode, type CompilerDOM } from '@vue/language-core'; import type * as vscode from 'vscode-languageserver-protocol'; export function create(ts: typeof import('typescript')): LanguageServicePlugin { @@ -26,7 +26,7 @@ export function create(ts: typeof import('typescript')): LanguageServicePlugin { const templateStartOffset = template.startTagEnd; const result: vscode.CodeAction[] = []; - for (const node of eachElementNode(template.ast)) { + for (const node of forEachElementNode(template.ast)) { if (startOffset > templateStartOffset + node.loc.end.offset || endOffset < templateStartOffset + node.loc.start.offset) { return; } diff --git a/packages/typescript-plugin/lib/common.ts b/packages/typescript-plugin/lib/common.ts index 902917ea75..6afde0a361 100644 --- a/packages/typescript-plugin/lib/common.ts +++ b/packages/typescript-plugin/lib/common.ts @@ -123,11 +123,11 @@ export function getComponentSpans( ...validComponentNames, ...validComponentNames.map(vue.hyphenateTag), ]); - template.ast?.children.forEach(function visit(node) { - if (node.loc.end.offset <= spanTemplateRange.start || node.loc.start.offset >= (spanTemplateRange.start + spanTemplateRange.length)) { - return; - } - if (node.type === 1 satisfies vue.CompilerDOM.NodeTypes.ELEMENT) { + if (template.ast) { + for (const node of vue.forEachElementNode(template.ast)) { + if (node.loc.end.offset <= spanTemplateRange.start || node.loc.start.offset >= (spanTemplateRange.start + spanTemplateRange.length)) { + continue; + } if (components.has(node.tag)) { let start = node.loc.start.offset; if (template.lang === 'html') { @@ -144,22 +144,7 @@ export function getComponentSpans( }); } } - for (const child of node.children) { - visit(child); - } } - else if (node.type === 9 satisfies vue.CompilerDOM.NodeTypes.IF) { - for (const branch of node.branches) { - for (const child of branch.children) { - visit(child); - } - } - } - else if (node.type === 11 satisfies vue.CompilerDOM.NodeTypes.FOR) { - for (const child of node.children) { - visit(child); - } - } - }); + } return result; }