Skip to content

Commit be03d86

Browse files
authored
feat: improved tracking of js expression values (#260)
* feat: improved tracking of js expression values * Create young-roses-peel.md
1 parent 830f1e4 commit be03d86

File tree

11 files changed

+244
-99
lines changed

11 files changed

+244
-99
lines changed

.changeset/young-roses-peel.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-vue-scoped-css": minor
3+
---
4+
5+
feat: improved tracking of js expression values

lib/styles/context/vue-components/index.ts

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,16 @@ const traverseNodes = vueAST.traverseNodes;
99

1010
const UNKNOWN = Symbol("unknown");
1111

12-
type DataPropertyNode = AST.ESLintExpression | AST.ESLintPattern;
13-
type ComputedPropertyNode = AST.ESLintExpression | AST.ESLintPattern;
1412
type Properties = {
1513
data:
1614
| {
17-
[key: string]: DataPropertyNode[];
15+
[key: string]: AST.ESLintExpression[];
1816
[UNKNOWN]?: true;
1917
}
2018
| typeof UNKNOWN;
2119
computed:
2220
| {
23-
[key: string]: ComputedPropertyNode[];
21+
[key: string]: AST.ESLintExpression[];
2422
[UNKNOWN]?: true;
2523
}
2624
| typeof UNKNOWN;
@@ -44,14 +42,13 @@ export class VueComponentContext {
4442
* @param {string} name property name
4543
* @returns {ASTNode[]} the Vue component property value nodes
4644
*/
47-
public findVueComponentProperty(
48-
name: string
49-
):
50-
| (AST.ESLintBlockStatement | AST.ESLintExpression | AST.ESLintPattern)[]
51-
| null {
45+
public findVueComponentProperty(name: string): AST.ESLintExpression[] | null {
5246
const properties =
5347
this.properties ||
54-
(this.properties = extractVueComponentPropertes(this.node, this.context));
48+
(this.properties = extractVueComponentProperties(
49+
this.node,
50+
this.context
51+
));
5552

5653
if (properties[UNKNOWN]) {
5754
return null;
@@ -108,7 +105,7 @@ export function createVueComponentContext(
108105
/**
109106
* Extract properties and properties value nodes, of Vue component.
110107
*/
111-
function extractVueComponentPropertes(
108+
function extractVueComponentProperties(
112109
vueNode: AST.ESLintObjectExpression,
113110
context: RuleContext
114111
): Properties {
@@ -123,9 +120,15 @@ function extractVueComponentPropertes(
123120
}
124121
const keyName = getPropertyOrIdentifierName(p);
125122
if (keyName === "data") {
126-
result.data = extractVueComponentData(p.value, context);
123+
result.data = extractVueComponentData(
124+
p.value as AST.ESLintExpression,
125+
context
126+
);
127127
} else if (keyName === "computed") {
128-
result.computed = extractVueComponentComputed(p.value, context);
128+
result.computed = extractVueComponentComputed(
129+
p.value as AST.ESLintExpression,
130+
context
131+
);
129132
}
130133
}
131134
return result;
@@ -135,11 +138,11 @@ function extractVueComponentPropertes(
135138
* Extract data, of Vue component.
136139
*/
137140
function extractVueComponentData(
138-
dataNode: AST.ESLintExpression | AST.ESLintPattern,
141+
dataNode: AST.ESLintExpression,
139142
context: RuleContext
140143
):
141144
| {
142-
[key: string]: DataPropertyNode[];
145+
[key: string]: AST.ESLintExpression[];
143146
[UNKNOWN]?: true;
144147
}
145148
| typeof UNKNOWN {
@@ -171,7 +174,7 @@ function extractVueComponentData(
171174
return UNKNOWN;
172175
}
173176
const data: {
174-
[key: string]: DataPropertyNode[];
177+
[key: string]: AST.ESLintExpression[];
175178
[UNKNOWN]?: true;
176179
} = {};
177180
for (const dataObj of dataNodes) {
@@ -188,7 +191,7 @@ function extractVueComponentData(
188191
data[UNKNOWN] = true;
189192
} else {
190193
const values = data[keyName] || (data[keyName] = []);
191-
values.push(prop.value);
194+
values.push(prop.value as AST.ESLintExpression);
192195
}
193196
} else {
194197
// Can not identify the key name.
@@ -203,11 +206,11 @@ function extractVueComponentData(
203206
* Extract computed properties, of Vue component.
204207
*/
205208
function extractVueComponentComputed(
206-
computedNode: AST.ESLintExpression | AST.ESLintPattern,
209+
computedNode: AST.ESLintExpression,
207210
context: RuleContext
208211
):
209212
| {
210-
[key: string]: ComputedPropertyNode[];
213+
[key: string]: AST.ESLintExpression[];
211214
[UNKNOWN]?: true;
212215
}
213216
| typeof UNKNOWN {
@@ -217,7 +220,7 @@ function extractVueComponentComputed(
217220
}
218221

219222
const computed: {
220-
[key: string]: ComputedPropertyNode[];
223+
[key: string]: AST.ESLintExpression[];
221224
[UNKNOWN]?: true;
222225
} = {};
223226
for (const p of computedNode.properties) {
@@ -233,15 +236,15 @@ function extractVueComponentComputed(
233236
continue;
234237
}
235238
const values = computed[keyName] || (computed[keyName] = []);
236-
const { value } = p;
237-
let func: AST.ESLintExpression | AST.ESLintPattern = value;
239+
const value = p.value as AST.ESLintExpression;
240+
let func: AST.ESLintExpression = value;
238241

239242
if (value.type === "ObjectExpression") {
240243
const get = value.properties
241244
.filter(isProperty)
242245
.find((prop) => getPropertyOrIdentifierName(prop) === "get");
243246
if (get) {
244-
func = get.value;
247+
func = get.value as AST.ESLintExpression;
245248
}
246249
}
247250
if (

lib/styles/selectors/query/attribute-tracker.ts

Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { getVueComponentContext } from "../../context";
21
import type { RuleContext, AST } from "../../../types";
32
import { isVDirective, isVBind, getArgument } from "../../../utils/templates";
3+
import type { ReferenceExpressions } from "./reference-expression";
4+
import { getReferenceExpressions } from "./reference-expression";
45

56
/**
67
* Gets the value nodes of attribute of given name as Array. Returns `null` If the given name can not be identified.
@@ -59,43 +60,4 @@ export function getAttributeValueNodes(
5960
return results;
6061
}
6162

62-
/**
63-
* Gets the reference expressions to the given expression.
64-
* @param {ASTNode} expression expression to track
65-
* @param {RuleContext} context ESLint rule context
66-
* @returns {ASTNode[]} reference expressions.
67-
*/
68-
export function getReferenceExpressions(
69-
expression: AttrExpressions,
70-
context: RuleContext
71-
): ReferenceExpressions[] | null {
72-
if (expression.type !== "Identifier") {
73-
return [expression];
74-
}
75-
const vueComponent = getVueComponentContext(context);
76-
if (!vueComponent) {
77-
return null;
78-
}
79-
// Identify expression references from Vue's `data` and `computed`.
80-
const props = vueComponent.findVueComponentProperty(expression.name);
81-
if (props == null) {
82-
// Property not found.
83-
return null;
84-
}
85-
return props;
86-
}
87-
88-
export type ReferenceExpressions =
89-
| AST.ESLintBlockStatement
90-
| AST.ESLintExpression
91-
| AST.ESLintPattern
92-
| AttrExpressions;
93-
94-
type AttrExpressions =
95-
| AST.ESLintExpression
96-
| AST.VFilterSequenceExpression
97-
| AST.VForExpression
98-
| AST.VOnExpression
99-
| AST.VSlotScopeExpression;
100-
10163
export type AttributeValueExpressions = ReferenceExpressions | AST.VLiteral;

lib/styles/selectors/query/index.ts

Lines changed: 13 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,15 @@ import type {
2929
} from "../../ast";
3030
import type { AST, RuleContext, ASTNode } from "../../../types";
3131
import type { ParsedQueryOptions } from "../../../options";
32-
import type { ReferenceExpressions } from "./attribute-tracker";
33-
import {
34-
getAttributeValueNodes,
35-
getReferenceExpressions,
36-
} from "./attribute-tracker";
32+
import { getAttributeValueNodes } from "./attribute-tracker";
3733
import type { ValidStyleContext } from "../../context";
3834
import { getVueComponentContext, getStyleContexts } from "../../context";
3935
import { getStringFromNode } from "../../utils/nodes";
4036
import { Template } from "../../template";
4137
import { isVElement, isTransitionElement } from "../../../utils/templates";
4238
import { isValidStyleContext } from "../../context/style";
39+
import type { ReferenceExpressions } from "./reference-expression";
40+
import { getReferenceExpressions } from "./reference-expression";
4341

4442
const TRANSITION_CLASS_BASES = [
4543
"enter",
@@ -926,27 +924,19 @@ function matchClassNameForArrayExpression(
926924
document: VueDocumentQueryContext
927925
): boolean {
928926
for (const e of expression.elements) {
929-
if (e.type === "Identifier") {
930-
if (withinTemplate(e, document)) {
931-
const expressions = getReferenceExpressions(e, document.context);
932-
if (expressions) {
933-
for (const e2 of expressions) {
934-
if (matchClassNameExpression(e2, className, document)) {
935-
return true;
936-
}
937-
}
938-
}
939-
} else {
940-
if (matchClassNameExpression(e, className, document)) {
941-
return true;
942-
}
943-
}
944-
} else if (e.type === "SpreadElement") {
927+
if (e.type === "SpreadElement") {
945928
if (matchClassNameExpression(e.argument, className, document)) {
946929
return true;
947930
}
948-
} else if (matchClassNameExpression(e, className, document)) {
949-
return true;
931+
} else {
932+
const expressions = getReferenceExpressions(e, document.context);
933+
if (expressions) {
934+
for (const e2 of expressions) {
935+
if (matchClassNameExpression(e2, className, document)) {
936+
return true;
937+
}
938+
}
939+
}
950940
}
951941
}
952942
return false;
@@ -1012,18 +1002,6 @@ function includesClassName(
10121002
return value.divide(/\s+/u).some((s) => className.match(s));
10131003
}
10141004

1015-
/**
1016-
* Checks whether the given node within `<template>`
1017-
*/
1018-
function withinTemplate(
1019-
expr: AST.ESLintIdentifier,
1020-
document: VueDocumentQueryContext
1021-
) {
1022-
const templateBody = document.context.getSourceCode().ast.templateBody;
1023-
const templateRange = templateBody?.range ?? [0, 0];
1024-
return templateRange[0] <= expr.range[0] && expr.range[1] <= templateRange[1];
1025-
}
1026-
10271005
/**
10281006
* Iterate unique items
10291007
*/
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { getVueComponentContext } from "../../context";
2+
import type { RuleContext, AST } from "../../../types";
3+
4+
export type ReferenceExpressions =
5+
| AST.ESLintExpression
6+
| AST.VFilterSequenceExpression
7+
| AST.VForExpression
8+
| AST.VOnExpression
9+
| AST.VSlotScopeExpression;
10+
/**
11+
* Gets the reference expressions to the given expression.
12+
* @param {ASTNode} expression expression to track
13+
* @param {RuleContext} context ESLint rule context
14+
* @returns {ASTNode[]} reference expressions.
15+
*/
16+
export function getReferenceExpressions(
17+
expression: ReferenceExpressions,
18+
context: RuleContext
19+
): ReferenceExpressions[] | null {
20+
if (expression.type === "ConditionalExpression") {
21+
const { consequent, alternate } = expression;
22+
return [
23+
...(getReferenceExpressions(consequent, context) ?? [consequent]),
24+
...(getReferenceExpressions(alternate, context) ?? [alternate]),
25+
];
26+
}
27+
if (expression.type === "LogicalExpression") {
28+
const { left, right } = expression;
29+
return [
30+
...(getReferenceExpressions(left, context) ?? [left]),
31+
...(getReferenceExpressions(right, context) ?? [right]),
32+
];
33+
}
34+
if (expression.type !== "Identifier") {
35+
return [expression];
36+
}
37+
if (!withinTemplate(expression, context)) {
38+
return [expression];
39+
}
40+
const vueComponent = getVueComponentContext(context);
41+
if (!vueComponent) {
42+
return null;
43+
}
44+
// Identify expression references from Vue's `data` and `computed`.
45+
const props = vueComponent.findVueComponentProperty(expression.name);
46+
if (props == null) {
47+
// Property not found.
48+
return null;
49+
}
50+
return props;
51+
}
52+
53+
/**
54+
* Checks whether the given node within `<template>`
55+
*/
56+
function withinTemplate(expr: AST.ESLintIdentifier, context: RuleContext) {
57+
const templateBody = context.getSourceCode().ast.templateBody;
58+
const templateRange = templateBody?.range ?? [0, 0];
59+
return templateRange[0] <= expr.range[0] && expr.range[1] <= templateRange[1];
60+
}

lib/styles/template/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ export class Template {
9696
node:
9797
| AST.ESLintBlockStatement
9898
| AST.ESLintExpression
99-
| AST.ESLintPattern
10099
| AST.ESLintPrivateIdentifier
101100
| AST.VLiteral
102101
| AST.VFilterSequenceExpression

0 commit comments

Comments
 (0)