Skip to content

Commit 79d7361

Browse files
committed
parse files, databases, & secrets
1 parent e566dbd commit 79d7361

5 files changed

Lines changed: 448 additions & 3 deletions

File tree

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`allows some simple static expressions 1`] = `
4+
[
5+
Node {
6+
"argument": "foo.csv",
7+
"arguments": [
8+
Node {
9+
"end": 29,
10+
"left": Node {
11+
"end": 20,
12+
"raw": ""foo"",
13+
"start": 15,
14+
"type": "Literal",
15+
"value": "foo",
16+
},
17+
"operator": "+",
18+
"right": Node {
19+
"end": 29,
20+
"raw": "".csv"",
21+
"start": 23,
22+
"type": "Literal",
23+
"value": ".csv",
24+
},
25+
"start": 15,
26+
"type": "BinaryExpression",
27+
},
28+
],
29+
"callee": Node {
30+
"end": 14,
31+
"name": "FileAttachment",
32+
"start": 0,
33+
"type": "Identifier",
34+
},
35+
"end": 30,
36+
"optional": false,
37+
"start": 0,
38+
"type": "CallExpression",
39+
},
40+
]
41+
`;
42+
43+
exports[`allows some simple static expressions 2`] = `
44+
[
45+
Node {
46+
"argument": "foo1.csv",
47+
"arguments": [
48+
Node {
49+
"end": 35,
50+
"left": Node {
51+
"end": 26,
52+
"left": Node {
53+
"end": 20,
54+
"raw": ""foo"",
55+
"start": 15,
56+
"type": "Literal",
57+
"value": "foo",
58+
},
59+
"operator": "+",
60+
"right": Node {
61+
"end": 26,
62+
"raw": ""1"",
63+
"start": 23,
64+
"type": "Literal",
65+
"value": "1",
66+
},
67+
"start": 15,
68+
"type": "BinaryExpression",
69+
},
70+
"operator": "+",
71+
"right": Node {
72+
"end": 35,
73+
"raw": "".csv"",
74+
"start": 29,
75+
"type": "Literal",
76+
"value": ".csv",
77+
},
78+
"start": 15,
79+
"type": "BinaryExpression",
80+
},
81+
],
82+
"callee": Node {
83+
"end": 14,
84+
"name": "FileAttachment",
85+
"start": 0,
86+
"type": "Identifier",
87+
},
88+
"end": 36,
89+
"optional": false,
90+
"start": 0,
91+
"type": "CallExpression",
92+
},
93+
]
94+
`;
95+
96+
exports[`allows some simple static expressions 3`] = `
97+
[
98+
Node {
99+
"argument": "foo.csv",
100+
"arguments": [
101+
Node {
102+
"end": 29,
103+
"expressions": [
104+
Node {
105+
"end": 23,
106+
"raw": ""foo"",
107+
"start": 18,
108+
"type": "Literal",
109+
"value": "foo",
110+
},
111+
],
112+
"quasis": [
113+
Node {
114+
"end": 16,
115+
"start": 16,
116+
"tail": false,
117+
"type": "TemplateElement",
118+
"value": {
119+
"cooked": "",
120+
"raw": "",
121+
},
122+
},
123+
Node {
124+
"end": 28,
125+
"start": 24,
126+
"tail": true,
127+
"type": "TemplateElement",
128+
"value": {
129+
"cooked": ".csv",
130+
"raw": ".csv",
131+
},
132+
},
133+
],
134+
"start": 15,
135+
"type": "TemplateLiteral",
136+
},
137+
],
138+
"callee": Node {
139+
"end": 14,
140+
"name": "FileAttachment",
141+
"start": 0,
142+
"type": "Identifier",
143+
},
144+
"end": 30,
145+
"optional": false,
146+
"start": 0,
147+
"type": "CallExpression",
148+
},
149+
]
150+
`;
151+
152+
exports[`allows some simple static expressions 4`] = `
153+
[
154+
Node {
155+
"argument": "foo1.csv",
156+
"arguments": [
157+
Node {
158+
"end": 35,
159+
"expressions": [
160+
Node {
161+
"end": 29,
162+
"left": Node {
163+
"end": 23,
164+
"raw": ""foo"",
165+
"start": 18,
166+
"type": "Literal",
167+
"value": "foo",
168+
},
169+
"operator": "+",
170+
"right": Node {
171+
"end": 29,
172+
"raw": ""1"",
173+
"start": 26,
174+
"type": "Literal",
175+
"value": "1",
176+
},
177+
"start": 18,
178+
"type": "BinaryExpression",
179+
},
180+
],
181+
"quasis": [
182+
Node {
183+
"end": 16,
184+
"start": 16,
185+
"tail": false,
186+
"type": "TemplateElement",
187+
"value": {
188+
"cooked": "",
189+
"raw": "",
190+
},
191+
},
192+
Node {
193+
"end": 34,
194+
"start": 30,
195+
"tail": true,
196+
"type": "TemplateElement",
197+
"value": {
198+
"cooked": ".csv",
199+
"raw": ".csv",
200+
},
201+
},
202+
],
203+
"start": 15,
204+
"type": "TemplateLiteral",
205+
},
206+
],
207+
"callee": Node {
208+
"end": 14,
209+
"name": "FileAttachment",
210+
"start": 0,
211+
"type": "Identifier",
212+
},
213+
"end": 36,
214+
"optional": false,
215+
"start": 0,
216+
"type": "CallExpression",
217+
},
218+
]
219+
`;
220+
221+
exports[`finds files 1`] = `
222+
[
223+
Node {
224+
"argument": "foo.csv",
225+
"arguments": [
226+
Node {
227+
"end": 24,
228+
"raw": ""foo.csv"",
229+
"start": 15,
230+
"type": "Literal",
231+
"value": "foo.csv",
232+
},
233+
],
234+
"callee": Node {
235+
"end": 14,
236+
"name": "FileAttachment",
237+
"start": 0,
238+
"type": "Identifier",
239+
},
240+
"end": 25,
241+
"optional": false,
242+
"start": 0,
243+
"type": "CallExpression",
244+
},
245+
]
246+
`;
247+
248+
exports[`finds files 2`] = `
249+
[
250+
Node {
251+
"argument": "foo.csv",
252+
"arguments": [
253+
Node {
254+
"end": 24,
255+
"raw": "'foo.csv'",
256+
"start": 15,
257+
"type": "Literal",
258+
"value": "foo.csv",
259+
},
260+
],
261+
"callee": Node {
262+
"end": 14,
263+
"name": "FileAttachment",
264+
"start": 0,
265+
"type": "Identifier",
266+
},
267+
"end": 25,
268+
"optional": false,
269+
"start": 0,
270+
"type": "CallExpression",
271+
},
272+
]
273+
`;
274+
275+
exports[`finds files 3`] = `
276+
[
277+
Node {
278+
"argument": "foo.csv",
279+
"arguments": [
280+
Node {
281+
"end": 24,
282+
"expressions": [],
283+
"quasis": [
284+
Node {
285+
"end": 23,
286+
"start": 16,
287+
"tail": true,
288+
"type": "TemplateElement",
289+
"value": {
290+
"cooked": "foo.csv",
291+
"raw": "foo.csv",
292+
},
293+
},
294+
],
295+
"start": 15,
296+
"type": "TemplateLiteral",
297+
},
298+
],
299+
"callee": Node {
300+
"end": 14,
301+
"name": "FileAttachment",
302+
"start": 0,
303+
"type": "Identifier",
304+
},
305+
"end": 25,
306+
"optional": false,
307+
"start": 0,
308+
"type": "CallExpression",
309+
},
310+
]
311+
`;

src/javascript/features.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type {Node, CallExpression} from "acorn";
2+
import {getStringLiteralValue, isStringLiteral} from "./literal.js";
3+
import {findReferences} from "./references.js";
4+
import {syntaxError} from "./syntaxError.js";
5+
import {simple} from "./walk.js";
6+
7+
export type FeatureExpression = CallExpression & {argument: string};
8+
9+
/**
10+
* Returns all calls to the specified feature (e.g., FileAttachment) in the
11+
* specified body. Throws a SyntaxError if any of the calls are invalid (e.g.,
12+
* when FileAttachment is passed a dynamic argument).
13+
*/
14+
export function findFeatures(name: string, body: Node, input: string): FeatureExpression[] {
15+
const filter = (ref: {name: string}) => ref.name === name;
16+
const references = new Set(findReferences(body, {filterDeclaration: filter, filterReference: filter})); // prettier-ignore
17+
const calls: FeatureExpression[] = [];
18+
19+
simple(body, {
20+
CallExpression(node) {
21+
const {callee} = node;
22+
if (callee.type !== "Identifier" || !references.has(callee)) return;
23+
const args = node.arguments;
24+
if (args.length !== 1) throw syntaxError(`${name} requires a single literal string argument`, node, input); // prettier-ignore
25+
const [arg] = args;
26+
if (!isStringLiteral(arg)) throw syntaxError(`${name} requires a single literal string argument`, node, input); // prettier-ignore
27+
calls.push(Object.assign(node, {argument: getStringLiteralValue(arg)}));
28+
}
29+
});
30+
31+
return calls;
32+
}

src/javascript/files.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {expect, test} from "vitest";
2+
import {parseJavaScript} from "./parse.js";
3+
import type {FeatureExpression} from "./features.js";
4+
5+
function find(input: string): FeatureExpression[] {
6+
return parseJavaScript(input).files;
7+
}
8+
9+
test("finds files", () => {
10+
expect(find(`FileAttachment("foo.csv")`)).toMatchSnapshot();
11+
expect(find(`FileAttachment('foo.csv')`)).toMatchSnapshot();
12+
expect(find(`FileAttachment(\`foo.csv\`)`)).toMatchSnapshot();
13+
});
14+
15+
test("allows some simple static expressions", () => {
16+
expect(find(`FileAttachment("foo" + ".csv")`)).toMatchSnapshot();
17+
expect(find(`FileAttachment("foo" + "1" + ".csv")`)).toMatchSnapshot();
18+
expect(find(`FileAttachment(\`\${"foo"}.csv\`)`)).toMatchSnapshot();
19+
expect(find(`FileAttachment(\`\${"foo" + "1"}.csv\`)`)).toMatchSnapshot();
20+
expect(find(`FileAttachment("foo" + ".csv")`)[0].argument).toBe("foo.csv");
21+
expect(find(`FileAttachment("foo" + "1" + ".csv")`)[0].argument).toBe("foo1.csv");
22+
expect(find(`FileAttachment(\`\${"foo"}.csv\`)`)[0].argument).toBe("foo.csv");
23+
expect(find(`FileAttachment(\`\${"foo" + "1"}.csv\`)`)[0].argument).toBe("foo1.csv");
24+
});
25+
26+
test("disallows dynamic arguments", () => {
27+
expect(() => find(`FileAttachment("foo" + bar + ".csv")`)).toThrow(/literal string/);
28+
});
29+
30+
test("ignores shadowed references", () => {
31+
expect(find(`const FileAttachment = () => {};\nFileAttachment("foo.csv");`)).toStrictEqual([]);
32+
expect(find(`FileAttachment("foo.csv");\nconst FileAttachment = () => {};`)).toStrictEqual([]);
33+
expect(find(`FileAttachment("foo.csv");\nvar FileAttachment = () => {};`)).toStrictEqual([]);
34+
expect(find(`function FileAttachment() {}\nFileAttachment("foo.csv");`)).toStrictEqual([]);
35+
expect(find(`function FileAttachment() {}\nFileAttachment("foo" + Math.random());`)).toStrictEqual([]); // prettier-ignore
36+
});
37+
38+
test("ignores aliased references", () => {
39+
expect(find(`const F = FileAttachment;\nF("foo.csv");`)).toStrictEqual([]);
40+
});

0 commit comments

Comments
 (0)