Skip to content

Commit

Permalink
feat(language-core): support the use of sfc root comment to configure…
Browse files Browse the repository at this point in the history
… `vueCompilerOptions` (#4987)

Co-authored-by: Johnson Chu <[email protected]>
  • Loading branch information
KazariEX and johnsoncodehk authored Dec 20, 2024
1 parent 83b9c52 commit 5f11c44
Show file tree
Hide file tree
Showing 17 changed files with 156 additions and 50 deletions.
36 changes: 36 additions & 0 deletions extensions/vscode/syntaxes/vue.tmLanguage.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"name": "Vue",
"scopeName": "source.vue",
"patterns": [
{
"include": "#vue-comments"
},
{
"include": "text.html.basic#comment"
},
Expand Down Expand Up @@ -1278,6 +1281,39 @@
]
}
]
},
"vue-comments": {
"patterns": [
{
"include": "#vue-comments-key-value"
}
]
},
"vue-comments-key-value": {
"begin": "(<!--)\\s*(@)([\\w$]+)(?=\\s)",
"beginCaptures": {
"1": {
"name": "punctuation.definition.comment.vue"
},
"2": {
"name": "punctuation.definition.block.tag.comment.vue"
},
"3": {
"name": "storage.type.class.comment.vue"
}
},
"end": "(-->)",
"endCaptures": {
"1": {
"name": "punctuation.definition.comment.vue"
}
},
"name": "comment.block.vue",
"patterns": [
{
"include": "source.json#value"
}
]
}
}
}
11 changes: 4 additions & 7 deletions packages/language-core/lib/codegen/script/scriptSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ function* generateSetupFunction(

yield* generateScriptSectionPartiallyEnding(scriptSetup.name, scriptSetup.content.length, '#3632/scriptSetup.vue');
yield* generateMacros(options, ctx);
yield* generateDefineProp(options, scriptSetup);
yield* generateDefineProp(options);

if (scriptSetupRanges.defineProps?.typeArg && scriptSetupRanges.withDefaults?.arg) {
// fix https://github.com/vuejs/language-tools/issues/1187
Expand Down Expand Up @@ -324,12 +324,9 @@ function* generateMacros(
}
}

function* generateDefineProp(
options: ScriptCodegenOptions,
scriptSetup: NonNullable<Sfc['scriptSetup']>
): Generator<Code> {
const definePropProposalA = scriptSetup.content.trimStart().startsWith('// @experimentalDefinePropProposal=kevinEdition') || options.vueCompilerOptions.experimentalDefinePropProposal === 'kevinEdition';
const definePropProposalB = scriptSetup.content.trimStart().startsWith('// @experimentalDefinePropProposal=johnsonEdition') || options.vueCompilerOptions.experimentalDefinePropProposal === 'johnsonEdition';
function* generateDefineProp(options: ScriptCodegenOptions): Generator<Code> {
const definePropProposalA = options.vueCompilerOptions.experimentalDefinePropProposal === 'kevinEdition';
const definePropProposalB = options.vueCompilerOptions.experimentalDefinePropProposal === 'johnsonEdition';

if (definePropProposalA || definePropProposalB) {
yield `type __VLS_PropOptions<T> = Exclude<import('${options.vueCompilerOptions.lib}').Prop<T>, import('${options.vueCompilerOptions.lib}').PropType<T>>${endOfLine}`;
Expand Down
4 changes: 2 additions & 2 deletions packages/language-core/lib/parsers/scriptSetupRanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ export function parseScriptSetupRanges(
const useCssModule: UseCssModule[] = [];
const useSlots: UseSlots[] = [];
const useTemplateRef: UseTemplateRef[] = [];
const definePropProposalA = vueCompilerOptions.experimentalDefinePropProposal === 'kevinEdition' || ast.text.trimStart().startsWith('// @experimentalDefinePropProposal=kevinEdition');
const definePropProposalB = vueCompilerOptions.experimentalDefinePropProposal === 'johnsonEdition' || ast.text.trimStart().startsWith('// @experimentalDefinePropProposal=johnsonEdition');
const definePropProposalA = vueCompilerOptions.experimentalDefinePropProposal === 'kevinEdition';
const definePropProposalB = vueCompilerOptions.experimentalDefinePropProposal === 'johnsonEdition';
const text = ast.text;

const leadingCommentRanges = ts.getLeadingCommentRanges(text, 0)?.reverse() ?? [];
Expand Down
22 changes: 22 additions & 0 deletions packages/language-core/lib/parsers/vueCompilerOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { VueCompilerOptions } from '../types';

const syntaxReg = /^\s*@(?<key>.+?)\s+(?<value>.+?)\s*$/m;

export function parseVueCompilerOptions(comments: string[]): Partial<VueCompilerOptions> | undefined {
const entries = comments
.map(text => {
try {
const match = text.match(syntaxReg);
if (match) {
const { key, value } = match.groups ?? {};
return [key, JSON.parse(value)] as const;
}
}
catch { };
})
.filter(item => !!item);

if (entries.length) {
return Object.fromEntries(entries);
}
}
1 change: 1 addition & 0 deletions packages/language-core/lib/plugins/file-html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const plugin: VueLanguagePlugin = ({ vueCompilerOptions }) => {
descriptor: {
filename: fileName,
source: content,
comments: [],
template: null,
script: null,
scriptSetup: null,
Expand Down
20 changes: 14 additions & 6 deletions packages/language-core/lib/plugins/vue-tsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { generateScript } from '../codegen/script';
import { generateTemplate } from '../codegen/template';
import { parseScriptRanges } from '../parsers/scriptRanges';
import { parseScriptSetupRanges } from '../parsers/scriptSetupRanges';
import { parseVueCompilerOptions } from '../parsers/vueCompilerOptions';
import type { Code, Sfc, VueLanguagePlugin } from '../types';
import { resolveVueCompilerOptions } from '../utils/ts';

export const tsCodegen = new WeakMap<Sfc, ReturnType<typeof createTsx>>();

Expand Down Expand Up @@ -77,14 +79,20 @@ function createTsx(
: _sfc.script && _sfc.script.lang !== 'js' ? _sfc.script.lang
: 'js';
});
const vueCompilerOptions = computed(() => {
const options = parseVueCompilerOptions(_sfc.comments);
return options
? resolveVueCompilerOptions(options, ctx.vueCompilerOptions)
: ctx.vueCompilerOptions;
});
const scriptRanges = computed(() =>
_sfc.script
? parseScriptRanges(ts, _sfc.script.ast, !!_sfc.scriptSetup, false)
: undefined
);
const scriptSetupRanges = computed(() =>
_sfc.scriptSetup
? parseScriptSetupRanges(ts, _sfc.scriptSetup.ast, ctx.vueCompilerOptions)
? parseScriptSetupRanges(ts, _sfc.scriptSetup.ast, vueCompilerOptions.get())
: undefined
);
const scriptSetupBindingNames = Unstable.computedSet(
Expand Down Expand Up @@ -147,17 +155,17 @@ function createTsx(
});
const generatedTemplate = computed(() => {

if (ctx.vueCompilerOptions.skipTemplateCodegen || !_sfc.template) {
if (vueCompilerOptions.get().skipTemplateCodegen || !_sfc.template) {
return;
}

const codes: Code[] = [];
const codegen = generateTemplate({
ts,
compilerOptions: ctx.compilerOptions,
vueCompilerOptions: ctx.vueCompilerOptions,
vueCompilerOptions: vueCompilerOptions.get(),
template: _sfc.template,
edited: ctx.vueCompilerOptions.__test || (fileEditTimes.get(fileName) ?? 0) >= 2,
edited: vueCompilerOptions.get().__test || (fileEditTimes.get(fileName) ?? 0) >= 2,
scriptSetupBindingNames: scriptSetupBindingNames.get(),
scriptSetupImportComponentNames: scriptSetupImportComponentNames.get(),
destructuredPropNames: destructuredPropNames.get(),
Expand Down Expand Up @@ -188,9 +196,9 @@ function createTsx(
const codegen = generateScript({
ts,
compilerOptions: ctx.compilerOptions,
vueCompilerOptions: ctx.vueCompilerOptions,
vueCompilerOptions: vueCompilerOptions.get(),
sfc: _sfc,
edited: ctx.vueCompilerOptions.__test || (fileEditTimes.get(fileName) ?? 0) >= 2,
edited: vueCompilerOptions.get().__test || (fileEditTimes.get(fileName) ?? 0) >= 2,
fileName,
lang: lang.get(),
scriptRanges: scriptRanges.get(),
Expand Down
1 change: 1 addition & 0 deletions packages/language-core/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export interface SfcBlock {

export interface Sfc {
content: string;
comments: string[],
template: SfcBlock & {
ast: CompilerDOM.RootNode | undefined;
errors: CompilerDOM.CompilerError[];
Expand Down
13 changes: 12 additions & 1 deletion packages/language-core/lib/utils/parseSfc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ import type { ElementNode, SourceLocation } from '@vue/compiler-dom';
import * as compiler from '@vue/compiler-dom';
import type { CompilerError, SFCBlock, SFCDescriptor, SFCParseResult, SFCScriptBlock, SFCStyleBlock, SFCTemplateBlock } from '@vue/compiler-sfc';

declare module '@vue/compiler-sfc' {
interface SFCDescriptor {
comments: string[];
}
}

export function parse(source: string): SFCParseResult {

const errors: CompilerError[] = [];
Expand All @@ -19,6 +25,7 @@ export function parse(source: string): SFCParseResult {
const descriptor: SFCDescriptor = {
filename: 'anonymous.vue',
source,
comments: [],
template: null,
script: null,
scriptSetup: null,
Expand All @@ -29,7 +36,11 @@ export function parse(source: string): SFCParseResult {
shouldForceReload: () => false,
};
ast.children.forEach(node => {
if (node.type !== compiler.NodeTypes.ELEMENT) {
if (node.type === compiler.NodeTypes.COMMENT) {
descriptor.comments.push(node.content);
return;
}
else if (node.type !== compiler.NodeTypes.ELEMENT) {
return;
}
switch (node.tag) {
Expand Down
65 changes: 39 additions & 26 deletions packages/language-core/lib/utils/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,27 +220,24 @@ function getPartialVueCompilerOptions(
}
}

export function resolveVueCompilerOptions(vueOptions: Partial<VueCompilerOptions>): VueCompilerOptions {
const target = vueOptions.target ?? 3.3;
const lib = vueOptions.lib ?? 'vue';
function getDefaultOptions(options: Partial<VueCompilerOptions>): VueCompilerOptions {
const target = options.target ?? 3.3;
const lib = options.lib ?? 'vue';
return {
...vueOptions,
target,
extensions: vueOptions.extensions ?? ['.vue'],
vitePressExtensions: vueOptions.vitePressExtensions ?? [],
petiteVueExtensions: vueOptions.petiteVueExtensions ?? [],
lib,
jsxSlots: vueOptions.jsxSlots ?? false,
strictTemplates: vueOptions.strictTemplates ?? false,
skipTemplateCodegen: vueOptions.skipTemplateCodegen ?? false,
fallthroughAttributes: vueOptions.fallthroughAttributes ?? false,
dataAttributes: vueOptions.dataAttributes ?? [],
htmlAttributes: vueOptions.htmlAttributes ?? ['aria-*'],
optionsWrapper: vueOptions.optionsWrapper ?? (
target >= 2.7
? [`(await import('${lib}')).defineComponent(`, `)`]
: [`(await import('${lib}')).default.extend(`, `)`]
),
extensions: ['.vue'],
vitePressExtensions: [],
petiteVueExtensions: [],
jsxSlots: false,
strictTemplates: false,
skipTemplateCodegen: false,
fallthroughAttributes: false,
dataAttributes: [],
htmlAttributes: ['aria-*'],
optionsWrapper: target >= 2.7
? [`(await import('${lib}')).defineComponent(`, `)`]
: [`(await import('${lib}')).default.extend(`, `)`],
macros: {
defineProps: ['defineProps'],
defineSlots: ['defineSlots'],
Expand All @@ -249,24 +246,40 @@ export function resolveVueCompilerOptions(vueOptions: Partial<VueCompilerOptions
defineModel: ['defineModel'],
defineOptions: ['defineOptions'],
withDefaults: ['withDefaults'],
...vueOptions.macros,
},
composables: {
useAttrs: ['useAttrs'],
useCssModule: ['useCssModule'],
useSlots: ['useSlots'],
useTemplateRef: ['useTemplateRef', 'templateRef'],
...vueOptions.composables,
},
plugins: vueOptions.plugins ?? [],
plugins: [],
experimentalDefinePropProposal: false,
experimentalResolveStyleCssClasses: 'scoped',
experimentalModelPropName: null!
};
};

export function resolveVueCompilerOptions(
options: Partial<VueCompilerOptions>,
defaults: VueCompilerOptions = getDefaultOptions(options)
): VueCompilerOptions {
return {
...defaults,
...options,
macros: {
...defaults.macros,
...options.macros,
},
composables: {
...defaults.composables,
...options.composables,
},

// experimental
experimentalDefinePropProposal: vueOptions.experimentalDefinePropProposal ?? false,
experimentalResolveStyleCssClasses: vueOptions.experimentalResolveStyleCssClasses ?? 'scoped',
// https://github.com/vuejs/vue-next/blob/master/packages/compiler-dom/src/transforms/vModel.ts#L49-L51
// https://vuejs.org/guide/essentials/forms.html#form-input-bindings
experimentalModelPropName: Object.fromEntries(Object.entries(
vueOptions.experimentalModelPropName ?? {
options.experimentalModelPropName ?? defaults.experimentalModelPropName ?? {
'': {
input: true
},
Expand Down Expand Up @@ -301,4 +314,4 @@ export function setupGlobalTypes(rootDir: string, vueOptions: VueCompilerOptions
host.writeFile(globalTypesPath, globalTypesContents);
return { absolutePath: globalTypesPath };
} catch { }
}
}
11 changes: 11 additions & 0 deletions packages/language-core/lib/virtualFile/computedSfc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ export function computedSfc(
const content = computed(() => {
return snapshot.get().getText(0, snapshot.get().getLength());
});
const comments = computed<string[]>(oldValue => {
const newValue = parsed.get()?.descriptor.comments ?? [];
if (
oldValue?.length === newValue.length
&& oldValue.every((v, i) => v === newValue[i])
) {
return oldValue;
}
return newValue;
});
const template = computedNullableSfcBlock(
'template',
'html',
Expand Down Expand Up @@ -149,6 +159,7 @@ export function computedSfc(

return {
get content() { return content.get(); },
get comments() { return comments.get(); },
get template() { return template.get(); },
get script() { return script.get(); },
get scriptSetup() { return scriptSetup.get(); },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<!-- @experimentalDefinePropProposal "johnsonEdition" -->

<script setup lang="ts" generic="T">
// @experimentalDefinePropProposal=johnsonEdition
import { exactType } from '../../shared';

const a = defineProp<T>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<!-- @experimentalDefinePropProposal "johnsonEdition" -->

<script setup lang="ts">
// @experimentalDefinePropProposal=johnsonEdition
import { exactType } from '../../shared';

const a = defineProp<string>();
Expand Down
4 changes: 2 additions & 2 deletions test-workspace/tsc/passedFixtures/vue3/#3548/main.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts" generic="T">
// @experimentalDefinePropProposal=johnsonEdition
<!-- @experimentalDefinePropProposal "johnsonEdition" -->

<script setup lang="ts" generic="T">
defineProp<T>();
</script>
3 changes: 2 additions & 1 deletion test-workspace/tsc/passedFixtures/vue3/#4649/prop-comp.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<!-- @experimentalDefinePropProposal "kevinEdition" -->

<script lang="ts" setup>
// @experimentalDefinePropProposal=kevinEdition
import { exactType } from '../../shared';

const fooAlias = defineProp('foo', {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<!-- @experimentalDefinePropProposal "kevinEdition" -->

<script setup lang="ts">
// @experimentalDefinePropProposal=kevinEdition
import { exactType } from '../../shared';

interface Qux { qux: true };
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<!-- @experimentalDefinePropProposal "johnsonEdition" -->

<script setup lang="ts" generic="T">
// @experimentalDefinePropProposal=johnsonEdition
import { exactType } from '../../shared';

const a = defineProp<T>();
Expand Down
Loading

0 comments on commit 5f11c44

Please sign in to comment.