ast-search 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,148 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { afterEach, describe, expect, test } from "@jest/globals";
11
+ import { fsMock, jsBasics, emptyFile, reactComponent, reactListNoKey, vueSFCOnlyJS, } from "./setup";
12
+ import { runQuery, expandShorthands } from "../search";
13
+ import { getAstFromPath } from "../file";
14
+ jest.mock("node:fs/promises", () => (Object.assign(Object.assign({}, jest.requireActual("node:fs/promises")), { open: jest.fn().mockImplementation((path, flags) => {
15
+ return fsMock.promises.open(path, flags);
16
+ }) })));
17
+ describe("expandShorthands", () => {
18
+ test("expands known shorthands to Babel type names", () => {
19
+ expect(expandShorthands("call")).toBe("CallExpression");
20
+ expect(expandShorthands("this")).toBe("ThisExpression");
21
+ expect(expandShorthands("arrow")).toBe("ArrowFunctionExpression");
22
+ });
23
+ test("does not expand inside quoted attribute values", () => {
24
+ expect(expandShorthands('[callee.name="call"]')).toBe('[callee.name="call"]');
25
+ expect(expandShorthands('[name="arrow"]')).toBe('[name="arrow"]');
26
+ });
27
+ test("expands shorthands in selector position but not in value position", () => {
28
+ expect(expandShorthands('call[callee.name="fn"]')).toBe('CallExpression[callee.name="fn"]');
29
+ });
30
+ test("passes through unknown selectors unchanged", () => {
31
+ expect(expandShorthands("FunctionDeclaration")).toBe("FunctionDeclaration");
32
+ expect(expandShorthands("JSXElement")).toBe("JSXElement");
33
+ });
34
+ });
35
+ describe("runQuery — type selectors", () => {
36
+ let ast;
37
+ let file;
38
+ let source;
39
+ afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
40
+ yield file.close();
41
+ }));
42
+ test("finds FunctionDeclaration nodes", () => __awaiter(void 0, void 0, void 0, function* () {
43
+ ({ ast, file, source } = yield getAstFromPath(jsBasics));
44
+ const matches = runQuery("FunctionDeclaration", ast, source, jsBasics);
45
+ expect(matches.length).toBeGreaterThan(0);
46
+ matches.forEach((m) => expect(m.source).toBeTruthy());
47
+ }));
48
+ test("finds CallExpression via shorthand 'call'", () => __awaiter(void 0, void 0, void 0, function* () {
49
+ ({ ast, file, source } = yield getAstFromPath(vueSFCOnlyJS));
50
+ const matches = runQuery("call", ast, source);
51
+ expect(matches.length).toBeGreaterThan(0);
52
+ }));
53
+ test("returns empty array for empty file", () => __awaiter(void 0, void 0, void 0, function* () {
54
+ ({ ast, file, source } = yield getAstFromPath(emptyFile));
55
+ const matches = runQuery("FunctionDeclaration", ast, source);
56
+ expect(matches).toHaveLength(0);
57
+ }));
58
+ test("match includes file, line, col, and source", () => __awaiter(void 0, void 0, void 0, function* () {
59
+ ({ ast, file, source } = yield getAstFromPath(jsBasics));
60
+ const matches = runQuery("FunctionDeclaration", ast, source, jsBasics);
61
+ expect(matches[0]).toMatchObject({
62
+ file: jsBasics,
63
+ line: expect.any(Number),
64
+ col: expect.any(Number),
65
+ source: expect.any(String),
66
+ });
67
+ expect(matches[0].line).toBeGreaterThan(0);
68
+ }));
69
+ });
70
+ describe("runQuery — attribute selectors", () => {
71
+ let ast;
72
+ let file;
73
+ let source;
74
+ afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
75
+ yield file.close();
76
+ }));
77
+ test("finds ThisExpression nodes in setup scope (via attribute-free selector)", () => __awaiter(void 0, void 0, void 0, function* () {
78
+ ({ ast, file, source } = yield getAstFromPath(vueSFCOnlyJS));
79
+ const matches = runQuery("ThisExpression", ast, source);
80
+ expect(matches.length).toBeGreaterThan(0);
81
+ }));
82
+ test("finds CallExpression with specific callee property name", () => __awaiter(void 0, void 0, void 0, function* () {
83
+ ({ ast, file, source } = yield getAstFromPath(reactListNoKey));
84
+ const matches = runQuery('CallExpression[callee.property.name="map"]', ast, source, reactListNoKey);
85
+ expect(matches.length).toBeGreaterThan(0);
86
+ matches.forEach((m) => expect(m.source).toContain("map"));
87
+ }));
88
+ test("finds JSXAttribute with specific name", () => __awaiter(void 0, void 0, void 0, function* () {
89
+ ({ ast, file, source } = yield getAstFromPath(reactListNoKey));
90
+ const matches = runQuery('JSXAttribute[name.name="key"]', ast, source, reactListNoKey);
91
+ expect(matches.length).toBeGreaterThan(0);
92
+ }));
93
+ test("does not find JSXAttribute[key] in ListNoKey component", () => __awaiter(void 0, void 0, void 0, function* () {
94
+ ({ ast, file, source } = yield getAstFromPath(reactListNoKey));
95
+ // ListNoKey has no key attribute on its <li> elements
96
+ const mapCalls = runQuery('VariableDeclarator[id.name="ListNoKey"] JSXAttribute[name.name="key"]', ast, source);
97
+ expect(mapCalls).toHaveLength(0);
98
+ }));
99
+ });
100
+ describe("runQuery — :has() and :not() pseudo-selectors", () => {
101
+ let ast;
102
+ let file;
103
+ let source;
104
+ afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
105
+ yield file.close();
106
+ }));
107
+ test(":has(ThisExpression) finds functions that use this", () => __awaiter(void 0, void 0, void 0, function* () {
108
+ ({ ast, file, source } = yield getAstFromPath(vueSFCOnlyJS));
109
+ const matches = runQuery("ObjectMethod:has(ThisExpression)", ast, source);
110
+ expect(matches.length).toBeGreaterThan(0);
111
+ }));
112
+ test(":not() excludes matches", () => __awaiter(void 0, void 0, void 0, function* () {
113
+ ({ ast, file, source } = yield getAstFromPath(reactComponent));
114
+ const withHook = runQuery("CallExpression[callee.name]", ast, source);
115
+ const withoutUseState = runQuery('CallExpression[callee.name]:not([callee.name="useState"])', ast, source);
116
+ expect(withoutUseState.length).toBeLessThan(withHook.length);
117
+ }));
118
+ test("map calls with no key — the target query", () => __awaiter(void 0, void 0, void 0, function* () {
119
+ ({ ast, file, source } = yield getAstFromPath(reactListNoKey));
120
+ // Components with .map() but no key attr anywhere in the component
121
+ const noKeyMatches = runQuery('VariableDeclarator:has(CallExpression[callee.property.name="map"]):not(:has(JSXAttribute[name.name="key"]))', ast, source, reactListNoKey);
122
+ const withKeyMatches = runQuery('VariableDeclarator:has(CallExpression[callee.property.name="map"]):has(JSXAttribute[name.name="key"])', ast, source, reactListNoKey);
123
+ expect(noKeyMatches.length).toBe(1);
124
+ expect(noKeyMatches[0].source).toContain("ListNoKey");
125
+ expect(withKeyMatches.length).toBe(1);
126
+ expect(withKeyMatches[0].source).toContain("ListWithKey");
127
+ }));
128
+ });
129
+ describe("runQuery — descendant combinator", () => {
130
+ let ast;
131
+ let file;
132
+ let source;
133
+ afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
134
+ yield file.close();
135
+ }));
136
+ test("space combinator finds descendants", () => __awaiter(void 0, void 0, void 0, function* () {
137
+ ({ ast, file, source } = yield getAstFromPath(vueSFCOnlyJS));
138
+ const matches = runQuery("ObjectMethod ThisExpression", ast, source);
139
+ expect(matches.length).toBeGreaterThan(0);
140
+ matches.forEach((m) => expect(m.source).toBe("this"));
141
+ }));
142
+ test("child combinator > is more restrictive than descendant", () => __awaiter(void 0, void 0, void 0, function* () {
143
+ ({ ast, file, source } = yield getAstFromPath(jsBasics));
144
+ const descendant = runQuery("VariableDeclaration Identifier", ast, source);
145
+ const child = runQuery("VariableDeclaration > VariableDeclarator", ast, source);
146
+ expect(descendant.length).toBeGreaterThanOrEqual(child.length);
147
+ }));
148
+ });