@hubspot/ui-extensions-dev-server 1.1.0 → 1.1.2
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.
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/lib/DevModeInterface.d.ts +9 -0
- package/dist/lib/DevModeInterface.js +36 -0
- package/dist/lib/DevModeParentInterface.d.ts +19 -0
- package/dist/lib/DevModeParentInterface.js +181 -0
- package/dist/lib/DevModeUnifiedInterface.d.ts +9 -0
- package/dist/lib/DevModeUnifiedInterface.js +118 -0
- package/dist/lib/DevServerState.d.ts +44 -0
- package/dist/lib/DevServerState.js +95 -0
- package/dist/lib/ExtensionsWebSocket.d.ts +25 -0
- package/dist/lib/ExtensionsWebSocket.js +110 -0
- package/dist/lib/__mocks__/config.d.ts +2 -0
- package/dist/lib/__mocks__/config.js +5 -0
- package/dist/lib/__mocks__/isExtensionFile.d.ts +5 -0
- package/dist/lib/__mocks__/isExtensionFile.js +11 -0
- package/dist/lib/__tests__/DevModeInterface.spec.d.ts +1 -0
- package/dist/lib/__tests__/DevModeInterface.spec.js +155 -0
- package/dist/lib/__tests__/DevModeParentInterface.spec.d.ts +1 -0
- package/dist/lib/__tests__/DevModeParentInterface.spec.js +179 -0
- package/dist/lib/__tests__/DevModeUnifiedInterface.spec.d.ts +1 -0
- package/dist/lib/__tests__/DevModeUnifiedInterface.spec.js +236 -0
- package/dist/lib/__tests__/ExtensionsWebSocket.spec.d.ts +1 -0
- package/dist/lib/__tests__/ExtensionsWebSocket.spec.js +304 -0
- package/dist/lib/__tests__/ast.spec.d.ts +1 -0
- package/dist/lib/__tests__/ast.spec.js +737 -0
- package/dist/lib/__tests__/build.spec.d.ts +1 -0
- package/dist/lib/__tests__/build.spec.js +159 -0
- package/dist/lib/__tests__/config.spec.d.ts +1 -0
- package/dist/lib/__tests__/config.spec.js +291 -0
- package/dist/lib/__tests__/dev.spec.d.ts +1 -0
- package/dist/lib/__tests__/dev.spec.js +80 -0
- package/dist/lib/__tests__/extensionsService.spec.d.ts +1 -0
- package/dist/lib/__tests__/extensionsService.spec.js +150 -0
- package/dist/lib/__tests__/factories.d.ts +48 -0
- package/dist/lib/__tests__/factories.js +32 -0
- package/dist/lib/__tests__/fixtures/extensionConfig.d.ts +182 -0
- package/dist/lib/__tests__/fixtures/extensionConfig.js +304 -0
- package/dist/lib/__tests__/fixtures/urls.d.ts +4 -0
- package/dist/lib/__tests__/fixtures/urls.js +4 -0
- package/dist/lib/__tests__/parsing-utils.spec.d.ts +1 -0
- package/dist/lib/__tests__/parsing-utils.spec.js +467 -0
- package/dist/lib/__tests__/plugins/codeBlockingPlugin.spec.d.ts +1 -0
- package/dist/lib/__tests__/plugins/codeBlockingPlugin.spec.js +112 -0
- package/dist/lib/__tests__/plugins/codeCheckingPlugin.spec.d.ts +1 -0
- package/dist/lib/__tests__/plugins/codeCheckingPlugin.spec.js +124 -0
- package/dist/lib/__tests__/plugins/devBuildPlugin.spec.d.ts +1 -0
- package/dist/lib/__tests__/plugins/devBuildPlugin.spec.js +396 -0
- package/dist/lib/__tests__/plugins/friendlyLoggingPlugin.spec.d.ts +1 -0
- package/dist/lib/__tests__/plugins/friendlyLoggingPlugin.spec.js +65 -0
- package/dist/lib/__tests__/plugins/manifestPlugin.spec.d.ts +1 -0
- package/dist/lib/__tests__/plugins/manifestPlugin.spec.js +455 -0
- package/dist/lib/__tests__/plugins/relevantModulesPlugin.spec.d.ts +1 -0
- package/dist/lib/__tests__/plugins/relevantModulesPlugin.spec.js +115 -0
- package/dist/lib/__tests__/server.spec.d.ts +1 -0
- package/dist/lib/__tests__/server.spec.js +152 -0
- package/dist/lib/__tests__/test-utils/ast.d.ts +1 -0
- package/dist/lib/__tests__/test-utils/ast.js +4 -0
- package/dist/lib/__tests__/utils.spec.d.ts +1 -0
- package/dist/lib/__tests__/utils.spec.js +176 -0
- package/dist/lib/ast.d.ts +16 -0
- package/dist/lib/ast.js +281 -0
- package/dist/lib/bin/cli.d.ts +2 -0
- package/dist/lib/bin/cli.js +143 -0
- package/dist/lib/build.d.ts +24 -0
- package/dist/lib/build.js +73 -0
- package/dist/lib/config.d.ts +7 -0
- package/dist/lib/config.js +124 -0
- package/dist/lib/constants.d.ts +32 -0
- package/dist/lib/constants.js +43 -0
- package/dist/lib/dev.d.ts +2 -0
- package/dist/lib/dev.js +58 -0
- package/dist/lib/extensionsService.d.ts +10 -0
- package/dist/lib/extensionsService.js +45 -0
- package/dist/lib/parsing-utils.d.ts +31 -0
- package/dist/lib/parsing-utils.js +289 -0
- package/dist/lib/plugins/codeBlockingPlugin.d.ts +8 -0
- package/dist/lib/plugins/codeBlockingPlugin.js +45 -0
- package/dist/lib/plugins/codeCheckingPlugin.d.ts +8 -0
- package/dist/lib/plugins/codeCheckingPlugin.js +93 -0
- package/dist/lib/plugins/devBuildPlugin.d.ts +8 -0
- package/dist/lib/plugins/devBuildPlugin.js +212 -0
- package/dist/lib/plugins/friendlyLoggingPlugin.d.ts +14 -0
- package/dist/lib/plugins/friendlyLoggingPlugin.js +36 -0
- package/dist/lib/plugins/manifestPlugin.d.ts +12 -0
- package/dist/lib/plugins/manifestPlugin.js +158 -0
- package/dist/lib/plugins/relevantModulesPlugin.d.ts +14 -0
- package/dist/lib/plugins/relevantModulesPlugin.js +33 -0
- package/dist/lib/server.d.ts +13 -0
- package/dist/lib/server.js +99 -0
- package/dist/lib/types.d.ts +290 -0
- package/dist/lib/types.js +12 -0
- package/dist/lib/utils.d.ts +25 -0
- package/dist/lib/utils.js +113 -0
- package/package.json +2 -1
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { traverseAbstractSyntaxTree } from "../ast.js";
|
|
3
|
+
import { getValueFromNode, isVariableImported, isIdentifierDefined, isFunctionInvoked, } from "../parsing-utils.js";
|
|
4
|
+
import { localParse } from "./test-utils/ast.js";
|
|
5
|
+
const emptyState = {
|
|
6
|
+
functions: {},
|
|
7
|
+
badImports: [],
|
|
8
|
+
variableDeclarations: new Map(),
|
|
9
|
+
dataDependencies: {
|
|
10
|
+
importedHooks: {},
|
|
11
|
+
dependencies: [],
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
const extensionPath = '/mock/extension/path';
|
|
15
|
+
const mockLogger = {
|
|
16
|
+
debug: vi.fn(),
|
|
17
|
+
info: vi.fn(),
|
|
18
|
+
warn: vi.fn(),
|
|
19
|
+
error: vi.fn(),
|
|
20
|
+
};
|
|
21
|
+
describe('Parsing Utils', () => {
|
|
22
|
+
describe('getValueFromNode', () => {
|
|
23
|
+
it('should extract literal values correctly', () => {
|
|
24
|
+
const stringProgram = localParse(`const foo = "hello world";`);
|
|
25
|
+
const numberProgram = localParse(`const foo = 42;`);
|
|
26
|
+
const booleanProgram = localParse(`const foo = true;`);
|
|
27
|
+
const nullProgram = localParse(`const foo = null;`);
|
|
28
|
+
// Extract the literal nodes from variable declarations
|
|
29
|
+
const stringLiteral = stringProgram.body[0].declarations[0].init;
|
|
30
|
+
const numberLiteral = numberProgram.body[0].declarations[0].init;
|
|
31
|
+
const booleanLiteral = booleanProgram.body[0].declarations[0]
|
|
32
|
+
.init;
|
|
33
|
+
const nullLiteral = nullProgram.body[0].declarations[0].init;
|
|
34
|
+
expect(getValueFromNode(stringLiteral, emptyState)).toEqual({
|
|
35
|
+
status: 'SUCCESS',
|
|
36
|
+
nodeValue: 'hello world',
|
|
37
|
+
});
|
|
38
|
+
expect(getValueFromNode(numberLiteral, emptyState)).toEqual({
|
|
39
|
+
status: 'SUCCESS',
|
|
40
|
+
nodeValue: 42,
|
|
41
|
+
});
|
|
42
|
+
expect(getValueFromNode(booleanLiteral, emptyState)).toEqual({
|
|
43
|
+
status: 'SUCCESS',
|
|
44
|
+
nodeValue: true,
|
|
45
|
+
});
|
|
46
|
+
expect(getValueFromNode(nullLiteral, emptyState)).toEqual({
|
|
47
|
+
status: 'SUCCESS',
|
|
48
|
+
nodeValue: null,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
it('should return identifier values correctly', () => {
|
|
52
|
+
const undefinedProgram = localParse(`const foo = undefined;`);
|
|
53
|
+
const nanProgram = localParse(`const foo = NaN;`);
|
|
54
|
+
const infinityProgram = localParse(`const foo = Infinity;`);
|
|
55
|
+
const identifierProgram = localParse(`const foo = 'myVar';`);
|
|
56
|
+
// Extract the identifier nodes from variable declarations
|
|
57
|
+
const undefinedIdentifier = undefinedProgram.body[0]
|
|
58
|
+
.declarations[0].init;
|
|
59
|
+
const nanIdentifier = nanProgram.body[0].declarations[0].init;
|
|
60
|
+
const infinityIdentifier = infinityProgram.body[0]
|
|
61
|
+
.declarations[0].init;
|
|
62
|
+
const identifierNode = identifierProgram.body[0].declarations[0]
|
|
63
|
+
.init;
|
|
64
|
+
const identifierOutput = traverseAbstractSyntaxTree(identifierProgram, [], extensionPath, mockLogger);
|
|
65
|
+
expect(getValueFromNode(undefinedIdentifier, emptyState)).toEqual({
|
|
66
|
+
status: 'SUCCESS',
|
|
67
|
+
nodeValue: undefined,
|
|
68
|
+
});
|
|
69
|
+
expect(getValueFromNode(nanIdentifier, emptyState)).toEqual({
|
|
70
|
+
status: 'SUCCESS',
|
|
71
|
+
nodeValue: NaN,
|
|
72
|
+
});
|
|
73
|
+
expect(getValueFromNode(infinityIdentifier, emptyState)).toEqual({
|
|
74
|
+
status: 'SUCCESS',
|
|
75
|
+
nodeValue: Infinity,
|
|
76
|
+
});
|
|
77
|
+
expect(getValueFromNode(identifierNode, identifierOutput)).toEqual({
|
|
78
|
+
status: 'SUCCESS',
|
|
79
|
+
nodeValue: 'myVar',
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
it('should extract array expressions correctly', () => {
|
|
83
|
+
const program = localParse(`const foo = [1, "hello", true, null];`);
|
|
84
|
+
const arrayExpression = program.body[0].declarations[0].init;
|
|
85
|
+
expect(getValueFromNode(arrayExpression, emptyState)).toEqual({
|
|
86
|
+
status: 'SUCCESS',
|
|
87
|
+
nodeValue: [1, 'hello', true, null],
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
it('should handle nested arrays correctly', () => {
|
|
91
|
+
const program = localParse(`const foo = [1, [2, 3], ["nested"]];`);
|
|
92
|
+
const arrayExpression = program.body[0].declarations[0].init;
|
|
93
|
+
expect(getValueFromNode(arrayExpression, emptyState)).toEqual({
|
|
94
|
+
status: 'SUCCESS',
|
|
95
|
+
nodeValue: [1, [2, 3], ['nested']],
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
it('should handle arrays with null/undefined elements', () => {
|
|
99
|
+
const program = localParse(`const foo = [1, null, 3, undefined];`);
|
|
100
|
+
const arrayExpression = program.body[0].declarations[0].init;
|
|
101
|
+
expect(getValueFromNode(arrayExpression, emptyState)).toEqual({
|
|
102
|
+
status: 'SUCCESS',
|
|
103
|
+
nodeValue: [1, null, 3, undefined],
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
it('should handle arrays with spread operators', () => {
|
|
107
|
+
const program = localParse(`const foo = [1, 2, 3]; const bar = [...foo, ...[4, 5], 6];`);
|
|
108
|
+
const output = traverseAbstractSyntaxTree(program, [], extensionPath, mockLogger);
|
|
109
|
+
const arrayExpression = program.body[1].declarations[0].init;
|
|
110
|
+
expect(getValueFromNode(arrayExpression, output)).toEqual({
|
|
111
|
+
status: 'SUCCESS',
|
|
112
|
+
nodeValue: [1, 2, 3, 4, 5, 6],
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
it('should fail gracefully for unsupported spread operators', () => {
|
|
116
|
+
const program = localParse(`const foo = () => [1, 2, 3]; const bar = [...foo(), ...[4, 5], 6];`);
|
|
117
|
+
const output = traverseAbstractSyntaxTree(program, [], extensionPath, mockLogger);
|
|
118
|
+
const arrayExpression = program.body[1].declarations[0].init;
|
|
119
|
+
const result = getValueFromNode(arrayExpression, output);
|
|
120
|
+
expect(result.status).toBe('FAIL');
|
|
121
|
+
expect(result.error).toBe('Array spread element failed: Unsupported SpreadElement type: CallExpression');
|
|
122
|
+
});
|
|
123
|
+
it('should extract simple object expressions correctly', () => {
|
|
124
|
+
const program = localParse(`const foo = { name: "John", age: 30 };`);
|
|
125
|
+
const objectExpression = program.body[0].declarations[0].init;
|
|
126
|
+
expect(getValueFromNode(objectExpression, emptyState)).toEqual({
|
|
127
|
+
status: 'SUCCESS',
|
|
128
|
+
nodeValue: {
|
|
129
|
+
name: 'John',
|
|
130
|
+
age: 30,
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
it('should handle object expressions with identifier keys', () => {
|
|
135
|
+
const program = localParse(`const foo = { name: "John", age: 30 };`);
|
|
136
|
+
const objectExpression = program.body[0].declarations[0].init;
|
|
137
|
+
expect(getValueFromNode(objectExpression, emptyState)).toEqual({
|
|
138
|
+
status: 'SUCCESS',
|
|
139
|
+
nodeValue: {
|
|
140
|
+
name: 'John',
|
|
141
|
+
age: 30,
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
it('should handle object expressions with literal keys', () => {
|
|
146
|
+
const program = localParse(`const foo = { "full-name": "John Doe", "age": 30 };`);
|
|
147
|
+
const objectExpression = program.body[0].declarations[0].init;
|
|
148
|
+
expect(getValueFromNode(objectExpression, emptyState)).toEqual({
|
|
149
|
+
status: 'SUCCESS',
|
|
150
|
+
nodeValue: {
|
|
151
|
+
'full-name': 'John Doe',
|
|
152
|
+
age: 30,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
it('should handle nested object expressions correctly', () => {
|
|
157
|
+
const program = localParse(`const foo = { person: { name: "John", details: { age: 30 } } };`);
|
|
158
|
+
const objectExpression = program.body[0].declarations[0].init;
|
|
159
|
+
expect(getValueFromNode(objectExpression, emptyState)).toEqual({
|
|
160
|
+
status: 'SUCCESS',
|
|
161
|
+
nodeValue: {
|
|
162
|
+
person: {
|
|
163
|
+
name: 'John',
|
|
164
|
+
details: {
|
|
165
|
+
age: 30,
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
it('should handle complex nested structures', () => {
|
|
172
|
+
const program = localParse(`const foo = { users: ["John", "Jane"], config: { enabled: true, count: 5 } };`);
|
|
173
|
+
const objectExpression = program.body[0].declarations[0].init;
|
|
174
|
+
expect(getValueFromNode(objectExpression, emptyState)).toEqual({
|
|
175
|
+
status: 'SUCCESS',
|
|
176
|
+
nodeValue: {
|
|
177
|
+
users: ['John', 'Jane'],
|
|
178
|
+
config: {
|
|
179
|
+
enabled: true,
|
|
180
|
+
count: 5,
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
it('should handle objects with mixed key types', () => {
|
|
186
|
+
const program = localParse(`const foo = { name: "John", "age": 30, 42: "answer" };`);
|
|
187
|
+
const objectExpression = program.body[0].declarations[0].init;
|
|
188
|
+
expect(getValueFromNode(objectExpression, emptyState)).toEqual({
|
|
189
|
+
status: 'SUCCESS',
|
|
190
|
+
nodeValue: {
|
|
191
|
+
name: 'John',
|
|
192
|
+
age: 30,
|
|
193
|
+
'42': 'answer',
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
it('should handle objects with spread operators', () => {
|
|
198
|
+
const program = localParse(`const subObject = { akey: 'astring', bkey: 'bstring' }; const bar = {...subObject, ckey: 'cstring', akey: 'anotherstring'}`);
|
|
199
|
+
const output = traverseAbstractSyntaxTree(program, [], extensionPath, mockLogger);
|
|
200
|
+
const objectExpression = program.body[1].declarations[0].init;
|
|
201
|
+
expect(getValueFromNode(objectExpression, output)).toEqual({
|
|
202
|
+
status: 'SUCCESS',
|
|
203
|
+
nodeValue: {
|
|
204
|
+
akey: 'anotherstring',
|
|
205
|
+
bkey: 'bstring',
|
|
206
|
+
ckey: 'cstring',
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
it('should return error message for unsupported node types', () => {
|
|
211
|
+
const program = localParse(`const foo = () => {};`);
|
|
212
|
+
const functionExpression = program.body[0].declarations[0].init;
|
|
213
|
+
const result = getValueFromNode(functionExpression, emptyState);
|
|
214
|
+
expect(result).toEqual({
|
|
215
|
+
status: 'FAIL',
|
|
216
|
+
error: 'Unsupported node type: ArrowFunctionExpression',
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
it('should handle empty arrays correctly', () => {
|
|
220
|
+
const program = localParse(`const foo = [];`);
|
|
221
|
+
const arrayExpression = program.body[0].declarations[0].init;
|
|
222
|
+
expect(getValueFromNode(arrayExpression, emptyState)).toEqual({
|
|
223
|
+
status: 'SUCCESS',
|
|
224
|
+
nodeValue: [],
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
it('should handle empty objects correctly', () => {
|
|
228
|
+
const program = localParse(`const foo = {};`);
|
|
229
|
+
const objectExpression = program.body[0].declarations[0].init;
|
|
230
|
+
expect(getValueFromNode(objectExpression, emptyState)).toEqual({
|
|
231
|
+
status: 'SUCCESS',
|
|
232
|
+
nodeValue: {},
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
it('should skip object properties with unsupported key types', () => {
|
|
236
|
+
// Create a mock node to simulate an unsupported key type
|
|
237
|
+
const mockObjectExpression = {
|
|
238
|
+
type: 'ObjectExpression',
|
|
239
|
+
properties: [
|
|
240
|
+
{
|
|
241
|
+
type: 'Property',
|
|
242
|
+
key: { type: 'MemberExpression' }, // Unsupported key type
|
|
243
|
+
value: { type: 'Literal', value: 'test' },
|
|
244
|
+
kind: 'init',
|
|
245
|
+
method: false,
|
|
246
|
+
shorthand: false,
|
|
247
|
+
computed: false,
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
};
|
|
251
|
+
expect(getValueFromNode(mockObjectExpression, emptyState)).toEqual({
|
|
252
|
+
status: 'SUCCESS',
|
|
253
|
+
nodeValue: {},
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
it('should handle template literals with various interpolations', () => {
|
|
257
|
+
const testCases = [
|
|
258
|
+
{
|
|
259
|
+
code: `const name = 'John'; const result = \`Hello \${name}\`;`,
|
|
260
|
+
expected: 'Hello John',
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
code: `const first = 'John'; const last = 'Doe'; const result = \`\${first} \${last}\`;`,
|
|
264
|
+
expected: 'John Doe',
|
|
265
|
+
},
|
|
266
|
+
{ code: `const result = \`Hello World\`;`, expected: 'Hello World' },
|
|
267
|
+
{
|
|
268
|
+
code: `const name = 'John'; const result = \`\${name}\`;`,
|
|
269
|
+
expected: 'John',
|
|
270
|
+
},
|
|
271
|
+
{ code: `const result = \`\`;`, expected: '' },
|
|
272
|
+
{
|
|
273
|
+
code: `const age = 25; const result = \`Age: \${age}\`;`,
|
|
274
|
+
expected: 'Age: 25',
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
code: `const flag = true; const result = \`Status: \${flag}\`;`,
|
|
278
|
+
expected: 'Status: true',
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
code: `const val = null; const result = \`Value: \${val}\`;`,
|
|
282
|
+
expected: 'Value: null',
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
code: `const arr = ['a', 'b']; const result = \`List: \${arr}\`;`,
|
|
286
|
+
expected: 'List: a,b',
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
code: `const obj = {name: 'test'}; const result = \`Obj: \${obj}\`;`,
|
|
290
|
+
expected: 'Obj: [object Object]',
|
|
291
|
+
},
|
|
292
|
+
];
|
|
293
|
+
testCases.forEach(({ code, expected }) => {
|
|
294
|
+
const program = localParse(code);
|
|
295
|
+
const output = traverseAbstractSyntaxTree(program, [], extensionPath, mockLogger);
|
|
296
|
+
const templateLiteral = program.body[program.body.length - 1]
|
|
297
|
+
.declarations[0].init;
|
|
298
|
+
expect(getValueFromNode(templateLiteral, output)).toEqual({
|
|
299
|
+
status: 'SUCCESS',
|
|
300
|
+
nodeValue: expected,
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
it('should handle unary expressions correctly', () => {
|
|
305
|
+
const testCases = [
|
|
306
|
+
{ code: `const num = 5; const result = -num;`, expected: -5 },
|
|
307
|
+
{ code: `const num = 5; const result = +num;`, expected: 5 },
|
|
308
|
+
{ code: `const flag = true; const result = !flag;`, expected: false },
|
|
309
|
+
{
|
|
310
|
+
code: `const val = 'test'; const result = typeof val;`,
|
|
311
|
+
expected: 'string',
|
|
312
|
+
},
|
|
313
|
+
];
|
|
314
|
+
testCases.forEach(({ code, expected }) => {
|
|
315
|
+
const program = localParse(code);
|
|
316
|
+
const output = traverseAbstractSyntaxTree(program, [], extensionPath, mockLogger);
|
|
317
|
+
const unaryExpression = program.body[program.body.length - 1]
|
|
318
|
+
.declarations[0].init;
|
|
319
|
+
expect(getValueFromNode(unaryExpression, output)).toEqual({
|
|
320
|
+
status: 'SUCCESS',
|
|
321
|
+
nodeValue: expected,
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
it('should fail for unsupported unary operators', () => {
|
|
326
|
+
const program = localParse(`const result = void 0;`);
|
|
327
|
+
const output = traverseAbstractSyntaxTree(program, [], extensionPath, mockLogger);
|
|
328
|
+
const unaryExpression = program.body[0].declarations[0].init;
|
|
329
|
+
expect(getValueFromNode(unaryExpression, output)).toEqual({
|
|
330
|
+
status: 'FAIL',
|
|
331
|
+
error: 'Unsupported unary operator: void',
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
it('should handle member expressions with variable resolution', () => {
|
|
335
|
+
const program = localParse(`
|
|
336
|
+
const config = { prefix: 'user', nested: { suffix: 'name' } };
|
|
337
|
+
const simple = config.prefix;
|
|
338
|
+
const nested = config.nested.suffix;
|
|
339
|
+
const deep = config.nested;
|
|
340
|
+
`);
|
|
341
|
+
const output = traverseAbstractSyntaxTree(program, [], extensionPath, mockLogger);
|
|
342
|
+
const simpleMember = program.body[1].declarations[0].init;
|
|
343
|
+
const nestedMember = program.body[2].declarations[0].init;
|
|
344
|
+
const deepMember = program.body[3].declarations[0].init;
|
|
345
|
+
expect(getValueFromNode(simpleMember, output)).toEqual({
|
|
346
|
+
status: 'SUCCESS',
|
|
347
|
+
nodeValue: 'user',
|
|
348
|
+
});
|
|
349
|
+
expect(getValueFromNode(nestedMember, output)).toEqual({
|
|
350
|
+
status: 'SUCCESS',
|
|
351
|
+
nodeValue: 'name',
|
|
352
|
+
});
|
|
353
|
+
expect(getValueFromNode(deepMember, output)).toEqual({
|
|
354
|
+
status: 'SUCCESS',
|
|
355
|
+
nodeValue: {
|
|
356
|
+
suffix: 'name',
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
it('should resolve identifiers to declared variables or return unsupported message as fallback', () => {
|
|
361
|
+
const program = localParse(`
|
|
362
|
+
const prefix = 'user';
|
|
363
|
+
const config = { type: 'contact' };
|
|
364
|
+
const resolvedVar = prefix;
|
|
365
|
+
const resolvedObj = config;
|
|
366
|
+
const undeclaredVar = someUndefinedVariable;
|
|
367
|
+
`);
|
|
368
|
+
const output = traverseAbstractSyntaxTree(program, [], extensionPath, mockLogger);
|
|
369
|
+
const resolved1 = program.body[2].declarations[0].init;
|
|
370
|
+
const resolved2 = program.body[3].declarations[0].init;
|
|
371
|
+
const undeclared = program.body[4].declarations[0].init;
|
|
372
|
+
expect(getValueFromNode(resolved1, output)).toEqual({
|
|
373
|
+
status: 'SUCCESS',
|
|
374
|
+
nodeValue: 'user',
|
|
375
|
+
});
|
|
376
|
+
expect(getValueFromNode(resolved2, output)).toEqual({
|
|
377
|
+
status: 'SUCCESS',
|
|
378
|
+
nodeValue: {
|
|
379
|
+
type: 'contact',
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
expect(getValueFromNode(undeclared, output)).toEqual({
|
|
383
|
+
status: 'FAIL',
|
|
384
|
+
error: 'Identifier someUndefinedVariable is not found',
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
describe('isVariableImported', () => {
|
|
389
|
+
it('should return true for ImportSpecifier with matching variable name', () => {
|
|
390
|
+
const program = localParse(`import { React } from 'react';`);
|
|
391
|
+
const importSpecifier = program.body[0].specifiers[0];
|
|
392
|
+
expect(isVariableImported(importSpecifier, 'React')).toBe(true);
|
|
393
|
+
expect(isVariableImported(importSpecifier, 'Component')).toBe(false);
|
|
394
|
+
});
|
|
395
|
+
it('should return true for ImportDefaultSpecifier with matching variable name', () => {
|
|
396
|
+
const program = localParse(`import React from 'react';`);
|
|
397
|
+
const importDefaultSpecifier = program.body[0].specifiers[0];
|
|
398
|
+
expect(isVariableImported(importDefaultSpecifier, 'React')).toBe(true);
|
|
399
|
+
expect(isVariableImported(importDefaultSpecifier, 'Component')).toBe(false);
|
|
400
|
+
});
|
|
401
|
+
it('should return true for ImportNamespaceSpecifier with matching variable name', () => {
|
|
402
|
+
const program = localParse(`import * as React from 'react';`);
|
|
403
|
+
const importNamespaceSpecifier = program.body[0].specifiers[0];
|
|
404
|
+
expect(isVariableImported(importNamespaceSpecifier, 'React')).toBe(true);
|
|
405
|
+
expect(isVariableImported(importNamespaceSpecifier, 'Component')).toBe(false);
|
|
406
|
+
});
|
|
407
|
+
it('should return false for null or undefined node', () => {
|
|
408
|
+
expect(isVariableImported(null, 'React')).toBe(false);
|
|
409
|
+
expect(isVariableImported(undefined, 'React')).toBe(false);
|
|
410
|
+
});
|
|
411
|
+
it('should return false for non-import node types', () => {
|
|
412
|
+
const program = localParse(`const foo = 'bar';`);
|
|
413
|
+
const variableDeclarator = program.body[0].declarations[0];
|
|
414
|
+
expect(isVariableImported(variableDeclarator, 'foo')).toBe(false);
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
describe('isIdentifierDefined', () => {
|
|
418
|
+
it('should return true for Identifier node with matching name and no restricting parent', () => {
|
|
419
|
+
const program = localParse(`const foo = bar;`);
|
|
420
|
+
const identifier = program.body[0].declarations[0].init;
|
|
421
|
+
expect(isIdentifierDefined(identifier, null, 'bar')).toBe(true);
|
|
422
|
+
expect(isIdentifierDefined(identifier, null, 'baz')).toBe(false);
|
|
423
|
+
});
|
|
424
|
+
it('should return false when parent is MemberExpression', () => {
|
|
425
|
+
const program = localParse(`const foo = obj.bar;`);
|
|
426
|
+
const memberExpression = program.body[0].declarations[0].init;
|
|
427
|
+
const identifier = memberExpression.property;
|
|
428
|
+
expect(isIdentifierDefined(identifier, memberExpression, 'bar')).toBe(false);
|
|
429
|
+
});
|
|
430
|
+
it('should return false when parent is CallExpression', () => {
|
|
431
|
+
const program = localParse(`const foo = func();`);
|
|
432
|
+
const callExpression = program.body[0].declarations[0].init;
|
|
433
|
+
const identifier = callExpression.callee;
|
|
434
|
+
expect(isIdentifierDefined(identifier, callExpression, 'func')).toBe(false);
|
|
435
|
+
});
|
|
436
|
+
it('should return false for non-Identifier node types', () => {
|
|
437
|
+
const program = localParse(`const foo = 'bar';`);
|
|
438
|
+
const literal = program.body[0].declarations[0].init;
|
|
439
|
+
expect(isIdentifierDefined(literal, null, 'bar')).toBe(false);
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
describe('isFunctionInvoked', () => {
|
|
443
|
+
it('should return true for CallExpression with matching function name', () => {
|
|
444
|
+
const program = localParse(`const result = myFunction();`);
|
|
445
|
+
const callExpression = program.body[0].declarations[0].init;
|
|
446
|
+
expect(isFunctionInvoked(callExpression, 'myFunction')).toBe(true);
|
|
447
|
+
expect(isFunctionInvoked(callExpression, 'otherFunction')).toBe(false);
|
|
448
|
+
});
|
|
449
|
+
it('should return false for CallExpression with member expression callee', () => {
|
|
450
|
+
const program = localParse(`const result = obj.method();`);
|
|
451
|
+
const callExpression = program.body[0].declarations[0].init;
|
|
452
|
+
expect(isFunctionInvoked(callExpression, 'method')).toBe(false);
|
|
453
|
+
});
|
|
454
|
+
it('should return false for non-CallExpression node types', () => {
|
|
455
|
+
const program = localParse(`const foo = 'bar';`);
|
|
456
|
+
const literal = program.body[0].declarations[0].init;
|
|
457
|
+
expect(isFunctionInvoked(literal, 'bar')).toBe(false);
|
|
458
|
+
});
|
|
459
|
+
it('should return false for CallExpression without callee name', () => {
|
|
460
|
+
const mockCallExpression = {
|
|
461
|
+
type: 'CallExpression',
|
|
462
|
+
callee: { type: 'Literal', value: 'test' },
|
|
463
|
+
};
|
|
464
|
+
expect(isFunctionInvoked(mockCallExpression, 'test')).toBe(false);
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
vi.mock('../../ast', () => ({
|
|
3
|
+
traverseAbstractSyntaxTree: vi.fn((input) => {
|
|
4
|
+
if (input === 'invalid') {
|
|
5
|
+
return {
|
|
6
|
+
functions: {
|
|
7
|
+
require: {
|
|
8
|
+
invoked: true,
|
|
9
|
+
scope: 'Global',
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
if (input === 'outOfBounds') {
|
|
15
|
+
return {
|
|
16
|
+
functions: {},
|
|
17
|
+
badImports: ['/outside/path/file'],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
functions: {
|
|
22
|
+
require: {
|
|
23
|
+
invoked: true,
|
|
24
|
+
defined: true,
|
|
25
|
+
scope: 'Local',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}),
|
|
30
|
+
}));
|
|
31
|
+
import codeBlockingPlugin from "../../plugins/codeBlockingPlugin.js";
|
|
32
|
+
import { traverseAbstractSyntaxTree } from "../../ast.js";
|
|
33
|
+
describe('codeBlockingPlugin', () => {
|
|
34
|
+
let plugin;
|
|
35
|
+
let logger;
|
|
36
|
+
const extensionPath = '/mock/extension/path';
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
logger = {
|
|
39
|
+
info: vi.fn(),
|
|
40
|
+
error: vi.fn(),
|
|
41
|
+
debug: vi.fn(),
|
|
42
|
+
warn: vi.fn(),
|
|
43
|
+
};
|
|
44
|
+
plugin = codeBlockingPlugin({ logger, extensionPath });
|
|
45
|
+
});
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
vi.clearAllMocks();
|
|
48
|
+
});
|
|
49
|
+
describe('metadata', () => {
|
|
50
|
+
it('should create the correct plugin metadata', () => {
|
|
51
|
+
expect(plugin).toStrictEqual(expect.objectContaining({
|
|
52
|
+
name: 'ui-extensions-code-blocking-plugin',
|
|
53
|
+
transform: expect.any(Function),
|
|
54
|
+
}));
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe('transform', () => {
|
|
58
|
+
it('should log a warning when require is being invoked', () => {
|
|
59
|
+
const code = 'invalid';
|
|
60
|
+
// @ts-expect-error It does exist, I promise
|
|
61
|
+
plugin.parse = vi.fn((input) => {
|
|
62
|
+
return input;
|
|
63
|
+
});
|
|
64
|
+
// @ts-expect-error Deep breath TS, it's ok
|
|
65
|
+
plugin.transform(code, 'filename');
|
|
66
|
+
expect(logger.warn).toHaveBeenCalledWith('require statements are not supported, replace require statements with import');
|
|
67
|
+
// @ts-expect-error It does exist, I promise
|
|
68
|
+
expect(plugin.parse).toBeCalledTimes(1);
|
|
69
|
+
// @ts-expect-error It does exist, I promise
|
|
70
|
+
expect(plugin.parse).toBeCalledWith(code);
|
|
71
|
+
expect(traverseAbstractSyntaxTree).toHaveBeenCalledTimes(1);
|
|
72
|
+
});
|
|
73
|
+
it('should log a warning when an out of bounds import is detected', () => {
|
|
74
|
+
const code = 'outOfBounds';
|
|
75
|
+
// @ts-expect-error It does exist, I promise
|
|
76
|
+
plugin.parse = vi.fn((input) => {
|
|
77
|
+
return input;
|
|
78
|
+
});
|
|
79
|
+
// @ts-expect-error Deep breath TS, it's ok
|
|
80
|
+
plugin.transform(code, 'filename');
|
|
81
|
+
expect(logger.warn).toHaveBeenCalledWith('Importing files from outside of the extension directory is not supported. Please move the import /outside/path/file into the extension directory.');
|
|
82
|
+
// @ts-expect-error It does exist, I promise
|
|
83
|
+
expect(plugin.parse).toBeCalledTimes(1);
|
|
84
|
+
expect(traverseAbstractSyntaxTree).toHaveBeenCalledTimes(1);
|
|
85
|
+
});
|
|
86
|
+
it('should not parse node_modules', () => {
|
|
87
|
+
// @ts-expect-error It does exist, I promise
|
|
88
|
+
plugin.parse = vi.fn(() => {
|
|
89
|
+
return {};
|
|
90
|
+
});
|
|
91
|
+
// @ts-expect-error Deep breath TS, it's ok
|
|
92
|
+
plugin.transform('valid', 'node_modules/filename');
|
|
93
|
+
// @ts-expect-error It does exist, I promise
|
|
94
|
+
expect(plugin.parse).not.toHaveBeenCalled();
|
|
95
|
+
});
|
|
96
|
+
it('should not throw an error for valid code', () => {
|
|
97
|
+
const code = 'valid';
|
|
98
|
+
// @ts-expect-error It does exist, I promise
|
|
99
|
+
plugin.parse = vi.fn(() => {
|
|
100
|
+
return {};
|
|
101
|
+
});
|
|
102
|
+
// @ts-expect-error Deep breath TS, it's ok
|
|
103
|
+
const result = plugin.transform(code, 'filename');
|
|
104
|
+
// @ts-expect-error It does exist, I promise
|
|
105
|
+
expect(plugin.parse).toBeCalledTimes(1);
|
|
106
|
+
// @ts-expect-error It does exist, I promise
|
|
107
|
+
expect(plugin.parse).toBeCalledWith(code);
|
|
108
|
+
expect(traverseAbstractSyntaxTree).toHaveBeenCalledTimes(1);
|
|
109
|
+
expect(result.code).toBe(code);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|