Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(language-core): type support for CSS Modules API #4674

Merged
merged 11 commits into from
Aug 25, 2024
72 changes: 70 additions & 2 deletions packages/language-core/lib/codegen/script/scriptSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { endOfLine, generateSfcBlockSection, newLine } from '../common';
import { generateComponent, generateEmitsOption } from './component';
import type { ScriptCodegenContext } from './context';
import { ScriptCodegenOptions, codeFeatures } from './index';
import { generateTemplate } from './template';
import { generateCssClassProperty, generateTemplate } from './template';

export function* generateScriptSetupImports(
scriptSetup: NonNullable<Sfc['scriptSetup']>,
Expand Down Expand Up @@ -211,6 +211,34 @@ function* generateSetupFunction(
]);
}
}
if (scriptSetupRanges.cssModules.length) {
for (const { exp, arg } of scriptSetupRanges.cssModules) {
if (arg) {
setupCodeModifies.push([
[
` as Omit<__VLS_StyleModules, '$style'>[`,
generateSfcBlockSection(scriptSetup, arg.start, arg.end, codeFeatures.all),
`]`
],
exp.end,
exp.end
]);
}
else {
setupCodeModifies.push([
[
` as __VLS_StyleModules[`,
['', scriptSetup.name, exp.start, codeFeatures.verification],
`'$style'`,
['', scriptSetup.name, exp.end, codeFeatures.verification],
`]`
],
exp.end,
exp.end
]);
}
}
}
for (const { define } of scriptSetupRanges.templateRefs) {
if (define?.arg) {
setupCodeModifies.push([[`<__VLS_Refs[${scriptSetup.content.slice(define.arg.start, define.arg.end)}], keyof __VLS_Refs>`], define.arg.start - 1, define.arg.start - 1]);
Expand Down Expand Up @@ -247,6 +275,7 @@ function* generateSetupFunction(

yield* generateComponentProps(options, ctx, scriptSetup, scriptSetupRanges, definePropMirrors);
yield* generateModelEmits(options, scriptSetup, scriptSetupRanges);
yield* generateStyleModules(options, ctx);
yield* generateTemplate(options, ctx, false);
yield `type __VLS_Refs = ReturnType<typeof __VLS_template>['refs']${endOfLine}`;
yield `type __VLS_Slots = ReturnType<typeof __VLS_template>['slots']${endOfLine}`;
Expand Down Expand Up @@ -417,6 +446,45 @@ function* generateModelEmits(
yield endOfLine;
}

function* generateStyleModules(
options: ScriptCodegenOptions,
ctx: ScriptCodegenContext
): Generator<Code> {
const styles = options.sfc.styles.filter(style => style.module);
if (!styles.length) {
return;
}
yield `type __VLS_StyleModules = {${newLine}`;
for (let i = 0; i < styles.length; i++) {
const style = styles[i];
const { name, offset } = style.module!;
if (offset) {
yield [
name,
'main',
offset + 1,
codeFeatures.all
];
}
else {
yield name;
}
yield `: Record<string, string> & ${ctx.helperTypes.Prettify.name}<{}`;
for (const className of style.classNames) {
yield* generateCssClassProperty(
i,
className.text,
className.offset,
'string',
false
);
}
yield `>${endOfLine}`;
}
yield `}`;
yield endOfLine;
}

function* generateDefinePropType(
scriptSetup: NonNullable<Sfc['scriptSetup']>,
propName: string | undefined,
Expand Down Expand Up @@ -453,7 +521,7 @@ function getPropAndLocalName(
? 'modelValue'
: localName;
if (defineProp.name) {
propName = propName!.replace(/['"]+/g, '')
propName = propName!.replace(/['"]+/g, '');
}
return [propName, localName];
}
Expand Down
24 changes: 3 additions & 21 deletions packages/language-core/lib/codegen/script/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function* generateTemplate(
yield `function __VLS_template() {${newLine}`;
}
const templateCodegenCtx = createTemplateCodegenContext(new Set());
yield* generateCtx(options, ctx, isClassComponent);
yield* generateCtx(options, isClassComponent);
yield* generateTemplateContext(options, templateCodegenCtx);
yield* generateExportOptions(options);
yield* generateConstNameOption(options);
Expand Down Expand Up @@ -76,7 +76,6 @@ function* generateConstNameOption(options: ScriptCodegenOptions): Generator<Code

function* generateCtx(
options: ScriptCodegenOptions,
ctx: ScriptCodegenContext,
isClassComponent: boolean
): Generator<Code> {
yield `let __VLS_ctx!: `;
Expand All @@ -91,24 +90,7 @@ function* generateCtx(
}
/* CSS Module */
if (options.sfc.styles.some(style => style.module)) {
yield `& {${newLine}`;
for (let i = 0; i < options.sfc.styles.length; i++) {
const style = options.sfc.styles[i];
if (style.module) {
yield `${style.module}: Record<string, string> & ${ctx.helperTypes.Prettify.name}<{}`;
for (const className of style.classNames) {
yield* generateCssClassProperty(
i,
className.text,
className.offset,
'string',
false
);
}
yield `>${endOfLine}`;
}
}
yield `}`;
yield ` & __VLS_StyleModules`;
}
yield endOfLine;
}
Expand Down Expand Up @@ -177,7 +159,7 @@ function* generateTemplateContext(
yield `}${endOfLine}`;
}

function* generateCssClassProperty(
export function* generateCssClassProperty(
styleIndex: number,
classNameWithDot: string,
offset: number,
Expand Down
14 changes: 14 additions & 0 deletions packages/language-core/lib/parsers/scriptSetupRanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ export function parseScriptSetupRanges(
name?: string;
inheritAttrs?: string;
} = {};
const cssModules: {
exp: TextRange;
arg?: TextRange;
}[] = [];
const templateRefs: {
name?: string;
define?: ReturnType<typeof parseDefineFunction>;
Expand Down Expand Up @@ -108,6 +112,7 @@ export function parseScriptSetupRanges(
emits,
expose,
options,
cssModules,
defineProp,
templateRefs,
};
Expand Down Expand Up @@ -371,6 +376,15 @@ export function parseScriptSetupRanges(
define
});
}
else if (vueCompilerOptions.composibles.useCssModule.includes(callText)) {
const module: (typeof cssModules)[number] = {
exp: _getStartEnd(node)
};
if (node.arguments.length) {
module.arg = _getStartEnd(node.arguments[0]);
}
cssModules.push(module);
}
}
ts.forEachChild(node, child => {
parents.push(node);
Expand Down
13 changes: 11 additions & 2 deletions packages/language-core/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export interface VueCompilerOptions {
withDefaults: string[];
templateRef: string[];
};
composibles: {
useCssModule: string[];
};
plugins: VueLanguagePlugin[];

// experimental
Expand Down Expand Up @@ -92,6 +95,13 @@ export interface SfcBlock {
attrs: Record<string, string | true>;
}

export interface SFCStyleOverride {
module?: {
name: string;
offset?: number;
};
}

export interface Sfc {
content: string;
template: SfcBlock & {
Expand All @@ -110,8 +120,7 @@ export interface Sfc {
genericOffset: number;
ast: ts.SourceFile;
} | undefined;
styles: readonly (SfcBlock & {
module: string | undefined;
styles: readonly (SfcBlock & SFCStyleOverride & {
scoped: boolean;
cssVars: {
text: string;
Expand Down
11 changes: 9 additions & 2 deletions packages/language-core/lib/utils/parseSfc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { CompilerError, SFCDescriptor, SFCBlock, SFCStyleBlock, SFCScriptBlock, SFCTemplateBlock, SFCParseResult } from '@vue/compiler-sfc';
import type { ElementNode, SourceLocation } from '@vue/compiler-dom';
import * as compiler from '@vue/compiler-dom';
import { SFCStyleOverride } from '../types';

export function parse(source: string): SFCParseResult {

Expand Down Expand Up @@ -90,7 +91,10 @@ function createBlock(node: ElementNode, source: string) {
end
};
const attrs: Record<string, any> = {};
const block: SFCBlock & Pick<SFCStyleBlock, 'scoped' | 'module'> & Pick<SFCScriptBlock, 'setup'> = {
const block: SFCBlock
& Pick<SFCStyleBlock, 'scoped'>
& Pick<SFCStyleOverride, 'module'>
& Pick<SFCScriptBlock, 'setup'> = {
type,
content,
loc,
Expand All @@ -110,7 +114,10 @@ function createBlock(node: ElementNode, source: string) {
block.scoped = true;
}
else if (p.name === 'module') {
block.module = attrs[p.name];
block.module = {
name: p.value?.content ?? '$style',
offset: p.value?.content ? p.value?.loc.start.offset - node.loc.start.offset : undefined
};
}
}
else if (type === 'script' && p.name === 'setup') {
Expand Down
3 changes: 3 additions & 0 deletions packages/language-core/lib/utils/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ export function resolveVueCompilerOptions(vueOptions: Partial<VueCompilerOptions
templateRef: ['templateRef', 'useTemplateRef'],
...vueOptions.macros,
},
composibles: {
useCssModule: ['useCssModule']
},
plugins: vueOptions.plugins ?? [],

// experimental
Expand Down
10 changes: 8 additions & 2 deletions packages/language-core/lib/virtualFile/computedSfc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type * as CompilerDOM from '@vue/compiler-dom';
import type { SFCBlock, SFCParseResult } from '@vue/compiler-sfc';
import { computed, computedArray, pauseTracking, resetTracking } from 'computeds';
import type * as ts from 'typescript';
import type { Sfc, SfcBlock, VueLanguagePluginReturn } from '../types';
import type { Sfc, SfcBlock, SFCStyleOverride, VueLanguagePluginReturn } from '../types';
import { parseCssClassNames } from '../utils/parseCssClassNames';
import { parseCssVars } from '../utils/parseCssVars';

Expand Down Expand Up @@ -117,7 +117,13 @@ export function computedSfc(
computed(() => parsed()?.descriptor.styles ?? []),
(block, i) => {
const base = computedSfcBlock('style_' + i, 'css', block);
const module = computed(() => typeof block().module === 'string' ? block().module as string : block().module ? '$style' : undefined);
const module = computed(() => {
const _module = block().module as SFCStyleOverride['module'];
return _module ? {
name: _module.name,
offset: _module.offset ? base.start + _module.offset : undefined
} : undefined;
});
const scoped = computed(() => !!block().scoped);
const cssVars = computed(() => [...parseCssVars(base.content)]);
const classNames = computed(() => [...parseCssClassNames(base.content)]);
Expand Down
70 changes: 70 additions & 0 deletions packages/language-server/tests/renaming.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,76 @@ describe('Renaming', async () => {
`);
});

it('#4673', async () => {
expect(
await requestRename('fixture.vue', 'vue', `
<script setup lang="ts">
import { useCssModule } from 'vue';
const $style = useCssModule();
const stylAlias = useCssModule('styl');
</script>

<template>
<div :class="styl|.foo">{{ }}</div>
</template>

<style module>
.foo { }
</style>

<style module="styl">
.foo { }
</style>
`, 'stylus')
).toMatchInlineSnapshot(`
{
"changes": {
"file://\${testWorkspacePath}/fixture.vue": [
{
"newText": "stylus",
"range": {
"end": {
"character": 22,
"line": 8,
},
"start": {
"character": 18,
"line": 8,
},
},
},
{
"newText": "stylus",
"range": {
"end": {
"character": 23,
"line": 15,
},
"start": {
"character": 19,
"line": 15,
},
},
},
{
"newText": "stylus",
"range": {
"end": {
"character": 40,
"line": 4,
},
"start": {
"character": 36,
"line": 4,
},
},
},
],
},
}
`);
});

const openedDocuments: TextDocument[] = [];

afterEach(async () => {
Expand Down