Skip to content

Commit 7b76dee

Browse files
committed
fix for destructuring
1 parent 9d1fd8b commit 7b76dee

File tree

2 files changed

+145
-35
lines changed

2 files changed

+145
-35
lines changed

src/svelte-config/parser.ts

Lines changed: 131 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
import type { StaticSvelteConfig } from ".";
2-
import { getEspree } from "../parser/espree";
3-
import type {
4-
Program,
5-
ExportDefaultDeclaration,
6-
Expression,
7-
Identifier,
8-
} from "estree";
9-
import { getFallbackKeys, traverseNodes } from "../traverse";
2+
import type * as ESTree from "estree";
3+
import type { Scope } from "eslint";
104
import type { ScopeManager } from "eslint-scope";
5+
import { getFallbackKeys, traverseNodes } from "../traverse";
6+
import { getEspree } from "../parser/espree";
117
import { analyze } from "eslint-scope";
128
import { findVariable } from "../scope";
139

@@ -72,11 +68,11 @@ class EvaluatedProperties {
7268
}
7369

7470
function parseAst(
75-
ast: Program,
71+
ast: ESTree.Program,
7672
scopeManager: ScopeManager,
7773
): StaticSvelteConfig {
7874
const edd = ast.body.find(
79-
(node): node is ExportDefaultDeclaration =>
75+
(node): node is ESTree.ExportDefaultDeclaration =>
8076
node.type === "ExportDefaultDeclaration",
8177
);
8278
if (!edd) return {};
@@ -87,10 +83,10 @@ function parseAst(
8783
}
8884

8985
function parseSvelteConfigExpression(
90-
node: Expression,
86+
node: ESTree.Expression,
9187
scopeManager: ScopeManager,
9288
): StaticSvelteConfig {
93-
const tracked = new Map<Identifier, Expression | null>();
89+
const tracked = new Map<ESTree.Identifier, Scope.Definition | null>();
9490
const parsed = parseExpression(node);
9591
if (parsed?.type !== EvaluatedType.object) return {};
9692
const properties = parsed.properties;
@@ -124,61 +120,161 @@ function parseSvelteConfigExpression(
124120
}
125121
return result;
126122

127-
function parseExpression(node: Expression): Evaluated | null {
123+
function parseExpression(node: ESTree.Expression): Evaluated | null {
128124
if (node.type === "Literal") {
129125
return { type: EvaluatedType.literal, value: node.value };
130126
}
131127
if (node.type === "Identifier") {
132-
const expr = trackIdentifier(node);
133-
if (!expr) return null;
134-
return parseExpression(expr);
128+
return parseIdentifier(node);
135129
}
136130
if (node.type === "ObjectExpression") {
137131
const reversedProperties = [...node.properties].reverse();
138132
return {
139133
type: EvaluatedType.object,
140134
properties: new EvaluatedProperties((key) => {
135+
let hasUnknown = false;
141136
for (const prop of reversedProperties) {
142137
if (prop.type === "Property") {
143-
if (
144-
!prop.computed &&
145-
prop.key.type === "Identifier" &&
146-
prop.key.name === key
147-
) {
148-
return parseExpression(prop.value as Expression);
149-
}
150-
const evaluatedKey = parseExpression(prop.key as Expression);
151-
if (
152-
evaluatedKey?.type === EvaluatedType.literal &&
153-
String(evaluatedKey.value) === key
154-
) {
155-
return parseExpression(prop.value as Expression);
138+
if (!prop.computed && prop.key.type === "Identifier") {
139+
if (prop.key.name === key)
140+
return parseExpression(prop.value as ESTree.Expression);
141+
} else {
142+
const evaluatedKey = parseExpression(
143+
prop.key as ESTree.Expression,
144+
);
145+
if (evaluatedKey?.type === EvaluatedType.literal) {
146+
if (String(evaluatedKey.value) === key)
147+
return parseExpression(prop.value as ESTree.Expression);
148+
} else {
149+
hasUnknown = true;
150+
}
156151
}
157152
} else if (prop.type === "SpreadElement") {
153+
hasUnknown = true;
158154
const nesting = parseExpression(prop.argument);
159155
if (nesting?.type === EvaluatedType.object) {
160156
const value = nesting.properties.get(key);
161157
if (value) return value;
162158
}
163159
}
164160
}
165-
return null;
161+
return hasUnknown
162+
? null
163+
: { type: EvaluatedType.literal, value: undefined };
166164
}),
167165
};
168166
}
169167

170168
return null;
171169
}
172170

173-
function trackIdentifier(node: Identifier): Expression | null {
171+
function parseIdentifier(node: ESTree.Identifier) {
172+
const def = getIdentifierDefinition(node);
173+
if (!def) return null;
174+
if (def.type !== "Variable") return null;
175+
if (def.parent.kind !== "const" || !def.node.init) return null;
176+
const evaluated = parseExpression(def.node.init);
177+
if (!evaluated) return null;
178+
const assigns = parsePatternAssign(def.name, def.node.id);
179+
let result = evaluated;
180+
while (assigns.length) {
181+
const assign = assigns.shift()!;
182+
if (assign.type === "member") {
183+
if (result.type !== EvaluatedType.object) return null;
184+
const next = result.properties.get(assign.name);
185+
if (!next) return null;
186+
result = next;
187+
} else if (assign.type === "assignment") {
188+
if (
189+
result.type === EvaluatedType.literal &&
190+
result.value === undefined
191+
) {
192+
const next = parseExpression(assign.node.right);
193+
if (!next) return null;
194+
result = next;
195+
}
196+
}
197+
}
198+
return result;
199+
}
200+
201+
function getIdentifierDefinition(
202+
node: ESTree.Identifier,
203+
): Scope.Definition | null {
174204
if (tracked.has(node)) return tracked.get(node) || null;
175205
tracked.set(node, null);
176206
const variable = findVariable(scopeManager, node);
177207
if (!variable || variable.defs.length !== 1) return null;
178208
const def = variable.defs[0];
179-
if (def.type !== "Variable" || def.parent.kind !== "const") return null;
180-
const init = def.node.init || null;
181-
tracked.set(node, init);
182-
return init;
209+
tracked.set(node, def);
210+
if (
211+
def.type !== "Variable" ||
212+
def.parent.kind !== "const" ||
213+
def.node.id.type !== "Identifier" ||
214+
def.node.init?.type !== "Identifier"
215+
) {
216+
return def;
217+
}
218+
const newDef = getIdentifierDefinition(def.node.init);
219+
tracked.set(node, newDef);
220+
return newDef;
221+
}
222+
}
223+
224+
function parsePatternAssign(
225+
node: ESTree.Pattern,
226+
root: ESTree.Pattern,
227+
): (
228+
| { type: "member"; name: string }
229+
| { type: "assignment"; node: ESTree.AssignmentPattern }
230+
)[] {
231+
return parse(root) || [];
232+
233+
function parse(
234+
target: ESTree.Pattern,
235+
):
236+
| (
237+
| { type: "member"; name: string }
238+
| { type: "assignment"; node: ESTree.AssignmentPattern }
239+
)[]
240+
| null {
241+
if (node === target) {
242+
return [];
243+
}
244+
if (target.type === "Identifier") {
245+
return null;
246+
}
247+
if (target.type === "AssignmentPattern") {
248+
const left = parse(target.left);
249+
if (!left) return null;
250+
return [{ type: "assignment", node: target }, ...left];
251+
}
252+
if (target.type === "ObjectPattern") {
253+
for (const prop of target.properties) {
254+
if (prop.type === "Property") {
255+
const name =
256+
!prop.computed && prop.key.type === "Identifier"
257+
? prop.key.name
258+
: prop.key.type === "Literal"
259+
? String(prop.key.value)
260+
: null;
261+
if (!name) continue;
262+
const value = parse(prop.value);
263+
if (!value) return null;
264+
return [{ type: "member", name }, ...value];
265+
}
266+
}
267+
return null;
268+
}
269+
if (target.type === "ArrayPattern") {
270+
for (const [index, element] of target.elements.entries()) {
271+
if (!element) continue;
272+
const value = parse(element);
273+
if (!value) return null;
274+
return [{ type: "member", name: String(index) }, ...value];
275+
}
276+
return null;
277+
}
278+
return null;
183279
}
184280
}

tests/src/svelte-config/parser.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,20 @@ describe("parseConfig", () => {
4646
`,
4747
output: { compilerOptions: { runes: false } },
4848
},
49+
{
50+
code: `
51+
const {compilerOptions} = {compilerOptions:{runes:true}}
52+
export default {compilerOptions}
53+
`,
54+
output: { compilerOptions: { runes: true } },
55+
},
56+
{
57+
code: `
58+
const {compilerOptions = {runes:true}} = {}
59+
export default {compilerOptions}
60+
`,
61+
output: { compilerOptions: { runes: true } },
62+
},
4963
];
5064
for (const { code, output } of testCases) {
5165
it(code, () => {

0 commit comments

Comments
 (0)