Skip to content

Commit

Permalink
refactor(language-core): extract collectStyleScopedClassReferences
Browse files Browse the repository at this point in the history
  • Loading branch information
KazariEX committed Dec 24, 2024
1 parent 55638ff commit ad75e70
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 189 deletions.
190 changes: 3 additions & 187 deletions packages/language-core/lib/codegen/template/element.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import * as CompilerDOM from '@vue/compiler-dom';
import { camelize, capitalize } from '@vue/shared';
import type * as ts from 'typescript';
import { getNodeText } from '../../parsers/scriptSetupRanges';
import type { Code, VueCodeInformation } from '../../types';
import { hyphenateTag } from '../../utils/shared';
import { createVBindShorthandInlayHintInfo } from '../inlayHints';
import { collectVars, createTsAst, endOfLine, newLine, variableNameRegex, wrapWith } from '../utils';
import { collectVars, createTsAst, endOfLine, newLine, normalizeAttributeValue, variableNameRegex, wrapWith } from '../utils';
import { generateCamelized } from '../utils/camelized';
import type { TemplateCodegenContext } from './context';
import { generateElementChildren } from './elementChildren';
Expand All @@ -16,6 +14,7 @@ import type { TemplateCodegenOptions } from './index';
import { generateInterpolation } from './interpolation';
import { generateObjectProperty } from './objectProperty';
import { generatePropertyAccess } from './propertyAccess';
import { collectStyleScopedClassReferences } from './styleScopedClasses';
import { generateTemplateChild } from './templateChild';

const colonReg = /:/g;
Expand Down Expand Up @@ -420,7 +419,7 @@ function* generateVScope(

yield* generateElementDirectives(options, ctx, node);
const [refName, offset] = yield* generateReferencesForElements(options, ctx, node); // <el ref="foo" />
yield* generateReferencesForScopedCssClasses(options, ctx, node);
collectStyleScopedClassReferences(options, ctx, node);

if (inScope) {
yield `}${newLine}`;
Expand Down Expand Up @@ -618,193 +617,10 @@ function* generateReferencesForElements(
return [];
}

function* generateReferencesForScopedCssClasses(
options: TemplateCodegenOptions,
ctx: TemplateCodegenContext,
node: CompilerDOM.ElementNode
): Generator<Code> {
for (const prop of node.props) {
if (
prop.type === CompilerDOM.NodeTypes.ATTRIBUTE
&& prop.name === 'class'
&& prop.value
) {
if (options.template.lang === 'pug') {
const getClassOffset = Reflect.get(prop.value.loc.start, 'getClassOffset') as (offset: number) => number;
const content = prop.value.loc.source.slice(1, -1);

let startOffset = 1;
for (const className of content.split(' ')) {
if (className) {
ctx.scopedClasses.push({
source: 'template',
className,
offset: getClassOffset(startOffset),
});
}
startOffset += className.length + 1;
}
}
else {
let isWrapped = false;
const [content, startOffset] = normalizeAttributeValue(prop.value);
if (content) {
const classes = collectClasses(content, startOffset + (isWrapped ? 1 : 0));
ctx.scopedClasses.push(...classes);
}
else {
ctx.emptyClassOffsets.push(startOffset);
}
}
}
else if (
prop.type === CompilerDOM.NodeTypes.DIRECTIVE
&& prop.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION
&& prop.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION
&& prop.arg.content === 'class'
) {
const content = '`${' + prop.exp.content + '}`';
const startOffset = prop.exp.loc.start.offset - 3;

const { ts } = options;
const ast = ts.createSourceFile('', content, 99 satisfies ts.ScriptTarget.Latest);
const literals: ts.StringLiteralLike[] = [];

ts.forEachChild(ast, node => {
if (
!ts.isExpressionStatement(node) ||
!isTemplateExpression(node.expression)
) {
return;
}

const expression = node.expression.templateSpans[0].expression;

if (ts.isStringLiteralLike(expression)) {
literals.push(expression);
}

if (ts.isArrayLiteralExpression(expression)) {
walkArrayLiteral(expression);
}

if (ts.isObjectLiteralExpression(expression)) {
walkObjectLiteral(expression);
}
});

for (const literal of literals) {
if (literal.text) {
const classes = collectClasses(
literal.text,
literal.end - literal.text.length - 1 + startOffset
);
ctx.scopedClasses.push(...classes);
}
else {
ctx.emptyClassOffsets.push(literal.end - 1 + startOffset);
}
}

function walkArrayLiteral(node: ts.ArrayLiteralExpression) {
const { elements } = node;
for (const element of elements) {
if (ts.isStringLiteralLike(element)) {
literals.push(element);
}
else if (ts.isObjectLiteralExpression(element)) {
walkObjectLiteral(element);
}
}
}

function walkObjectLiteral(node: ts.ObjectLiteralExpression) {
const { properties } = node;
for (const property of properties) {
if (ts.isPropertyAssignment(property)) {
const { name } = property;
if (ts.isIdentifier(name)) {
walkIdentifier(name);
}
else if (ts.isStringLiteral(name)) {
literals.push(name);
}
else if (ts.isComputedPropertyName(name)) {
const { expression } = name;
if (ts.isStringLiteralLike(expression)) {
literals.push(expression);
}
}
}
else if (ts.isShorthandPropertyAssignment(property)) {
walkIdentifier(property.name);
}
}
}

function walkIdentifier(node: ts.Identifier) {
const text = getNodeText(ts, node, ast);
ctx.scopedClasses.push({
source: 'template',
className: text,
offset: node.end - text.length + startOffset
});
}
}
}
}

function camelizeComponentName(newName: string) {
return camelize('-' + newName);
}

function getTagRenameApply(oldName: string) {
return oldName === hyphenateTag(oldName) ? hyphenateTag : undefined;
}

function normalizeAttributeValue(node: CompilerDOM.TextNode): [string, number] {
let offset = node.loc.start.offset;
let content = node.loc.source;
if (
(content.startsWith(`'`) && content.endsWith(`'`))
|| (content.startsWith(`"`) && content.endsWith(`"`))
) {
offset++;
content = content.slice(1, -1);
}
return [content, offset];
}

function collectClasses(content: string, startOffset = 0) {
const classes: {
source: string;
className: string;
offset: number;
}[] = [];

let currentClassName = '';
let offset = 0;
for (const char of (content + ' ')) {
if (char.trim() === '') {
if (currentClassName !== '') {
classes.push({
source: 'template',
className: currentClassName,
offset: offset + startOffset
});
offset += currentClassName.length;
currentClassName = '';
}
offset += char.length;
}
else {
currentClassName += char;
}
}
return classes;
}

// isTemplateExpression is missing in tsc
function isTemplateExpression(node: ts.Node): node is ts.TemplateExpression {
return node.kind === 228 satisfies ts.SyntaxKind.TemplateExpression;
}
Loading

0 comments on commit ad75e70

Please sign in to comment.