@far-world-labs/verblets 0.1.1

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.
Files changed (167) hide show
  1. package/.eslintrc.json +42 -0
  2. package/.husky/pre-commit +4 -0
  3. package/.release-it.json +9 -0
  4. package/.vite.config.examples.js +8 -0
  5. package/.vite.config.js +8 -0
  6. package/docker-compose.yml +7 -0
  7. package/docs/README.md +41 -0
  8. package/docs/babel.config.js +3 -0
  9. package/docs/blog/2019-05-28-first-blog-post.md +12 -0
  10. package/docs/blog/2019-05-29-long-blog-post.md +44 -0
  11. package/docs/blog/2021-08-01-mdx-blog-post.mdx +20 -0
  12. package/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
  13. package/docs/blog/2021-08-26-welcome/index.md +25 -0
  14. package/docs/blog/authors.yml +17 -0
  15. package/docs/docs/api/bool.md +74 -0
  16. package/docs/docs/api/search.md +51 -0
  17. package/docs/docs/intro.md +47 -0
  18. package/docs/docs/tutorial-basics/_category_.json +8 -0
  19. package/docs/docs/tutorial-basics/congratulations.md +23 -0
  20. package/docs/docs/tutorial-basics/create-a-blog-post.md +34 -0
  21. package/docs/docs/tutorial-basics/create-a-document.md +57 -0
  22. package/docs/docs/tutorial-basics/create-a-page.md +43 -0
  23. package/docs/docs/tutorial-basics/deploy-your-site.md +31 -0
  24. package/docs/docs/tutorial-basics/markdown-features.mdx +152 -0
  25. package/docs/docs/tutorial-extras/_category_.json +7 -0
  26. package/docs/docs/tutorial-extras/img/docsVersionDropdown.png +0 -0
  27. package/docs/docs/tutorial-extras/img/localeDropdown.png +0 -0
  28. package/docs/docs/tutorial-extras/manage-docs-versions.md +55 -0
  29. package/docs/docs/tutorial-extras/translate-your-site.md +88 -0
  30. package/docs/docusaurus.config.js +120 -0
  31. package/docs/package.json +44 -0
  32. package/docs/sidebars.js +31 -0
  33. package/docs/src/components/HomepageFeatures/index.js +61 -0
  34. package/docs/src/components/HomepageFeatures/styles.module.css +11 -0
  35. package/docs/src/css/custom.css +30 -0
  36. package/docs/src/pages/index.js +43 -0
  37. package/docs/src/pages/index.module.css +23 -0
  38. package/docs/src/pages/markdown-page.md +7 -0
  39. package/docs/static/.nojekyll +0 -0
  40. package/docs/static/img/docusaurus-social-card.jpg +0 -0
  41. package/docs/static/img/docusaurus.png +0 -0
  42. package/docs/static/img/favicon.ico +0 -0
  43. package/docs/static/img/logo.svg +1 -0
  44. package/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
  45. package/docs/static/img/undraw_docusaurus_react.svg +170 -0
  46. package/docs/static/img/undraw_docusaurus_tree.svg +40 -0
  47. package/package.json +75 -0
  48. package/scripts/generate-chain/index.js +111 -0
  49. package/scripts/generate-lib/index.js +68 -0
  50. package/scripts/generate-test/index.js +111 -0
  51. package/scripts/generate-verblet/README.md +17 -0
  52. package/scripts/generate-verblet/index.js +110 -0
  53. package/scripts/run.sh +15 -0
  54. package/scripts/runner/index.js +30 -0
  55. package/scripts/simple-editor/README.md +34 -0
  56. package/scripts/simple-editor/index.js +68 -0
  57. package/scripts/summarize-files/index.js +46 -0
  58. package/src/chains/dismantle/dismantle.examples.js +0 -0
  59. package/src/chains/dismantle/index.examples.js +30 -0
  60. package/src/chains/dismantle/index.js +314 -0
  61. package/src/chains/dismantle/index.spec.js +33 -0
  62. package/src/chains/list/index.examples.js +72 -0
  63. package/src/chains/list/index.js +161 -0
  64. package/src/chains/list/index.spec.js +68 -0
  65. package/src/chains/list/schema.json +24 -0
  66. package/src/chains/questions/index.examples.js +68 -0
  67. package/src/chains/questions/index.js +136 -0
  68. package/src/chains/questions/index.spec.js +29 -0
  69. package/src/chains/scan-js/index.js +119 -0
  70. package/src/chains/sort/index.examples.js +40 -0
  71. package/src/chains/sort/index.js +113 -0
  72. package/src/chains/sort/index.spec.js +115 -0
  73. package/src/chains/summary-map/README.md +33 -0
  74. package/src/chains/summary-map/index.examples.js +57 -0
  75. package/src/chains/summary-map/index.js +208 -0
  76. package/src/chains/summary-map/index.spec.js +78 -0
  77. package/src/chains/test/index.js +118 -0
  78. package/src/chains/test-advice/index.js +36 -0
  79. package/src/constants/common.js +9 -0
  80. package/src/constants/messages.js +3 -0
  81. package/src/constants/openai.js +65 -0
  82. package/src/index.js +33 -0
  83. package/src/json-schemas/cars-test.json +11 -0
  84. package/src/json-schemas/index.js +18 -0
  85. package/src/json-schemas/intent.json +38 -0
  86. package/src/json-schemas/schema-dot-org-photograph.json +127 -0
  87. package/src/json-schemas/schema-dot-org-place.json +56 -0
  88. package/src/lib/any-signal/index.js +28 -0
  89. package/src/lib/chatgpt/index.js +143 -0
  90. package/src/lib/editor/index.js +31 -0
  91. package/src/lib/parse-js-parts/index.js +333 -0
  92. package/src/lib/parse-js-parts/index.spec.js +156 -0
  93. package/src/lib/path-aliases/index.js +39 -0
  94. package/src/lib/path-aliases/index.spec.js +70 -0
  95. package/src/lib/pave/index.js +34 -0
  96. package/src/lib/pave/index.spec.js +73 -0
  97. package/src/lib/prompt-cache/index.js +46 -0
  98. package/src/lib/retry/index.js +63 -0
  99. package/src/lib/retry/index.spec.js +86 -0
  100. package/src/lib/search-best-first/index.js +66 -0
  101. package/src/lib/search-js-files/code-features-property-definitions.json +123 -0
  102. package/src/lib/search-js-files/index.examples.js +22 -0
  103. package/src/lib/search-js-files/index.js +158 -0
  104. package/src/lib/search-js-files/index.spec.js +34 -0
  105. package/src/lib/search-js-files/scan-file.js +253 -0
  106. package/src/lib/shorten-text/index.js +30 -0
  107. package/src/lib/shorten-text/index.spec.js +68 -0
  108. package/src/lib/strip-numeric/index.js +5 -0
  109. package/src/lib/strip-response/index.js +35 -0
  110. package/src/lib/timed-abort-controller/index.js +41 -0
  111. package/src/lib/to-bool/index.js +8 -0
  112. package/src/lib/to-enum/index.js +14 -0
  113. package/src/lib/to-number/index.js +12 -0
  114. package/src/lib/to-number-with-units/index.js +51 -0
  115. package/src/lib/transcribe/index.js +61 -0
  116. package/src/prompts/README.md +15 -0
  117. package/src/prompts/as-enum.js +5 -0
  118. package/src/prompts/as-json-schema.js +9 -0
  119. package/src/prompts/as-object-with-schema.js +31 -0
  120. package/src/prompts/as-schema-org-text.js +17 -0
  121. package/src/prompts/as-schema-org-type.js +1 -0
  122. package/src/prompts/blog-post.js +7 -0
  123. package/src/prompts/code-features.js +28 -0
  124. package/src/prompts/constants.js +101 -0
  125. package/src/prompts/features-json-schema.js +27 -0
  126. package/src/prompts/generate-collection.js +26 -0
  127. package/src/prompts/generate-list.js +48 -0
  128. package/src/prompts/generate-questions.js +19 -0
  129. package/src/prompts/index.js +20 -0
  130. package/src/prompts/intent.js +66 -0
  131. package/src/prompts/output-succinct-names.js +3 -0
  132. package/src/prompts/select-from-threshold.js +18 -0
  133. package/src/prompts/sort.js +35 -0
  134. package/src/prompts/style.js +41 -0
  135. package/src/prompts/summarize.js +13 -0
  136. package/src/prompts/token-budget.js +3 -0
  137. package/src/prompts/wrap-list.js +14 -0
  138. package/src/prompts/wrap-variable.js +36 -0
  139. package/src/services/llm-model/index.js +114 -0
  140. package/src/services/llm-model/model.js +21 -0
  141. package/src/services/redis/index.js +84 -0
  142. package/src/verblets/auto/index.examples.js +28 -0
  143. package/src/verblets/auto/index.js +28 -0
  144. package/src/verblets/auto/index.spec.js +34 -0
  145. package/src/verblets/bool/index.examples.js +28 -0
  146. package/src/verblets/bool/index.js +28 -0
  147. package/src/verblets/bool/index.schema.json +14 -0
  148. package/src/verblets/bool/index.spec.js +35 -0
  149. package/src/verblets/enum/index.examples.js +33 -0
  150. package/src/verblets/enum/index.js +15 -0
  151. package/src/verblets/enum/index.spec.js +35 -0
  152. package/src/verblets/intent/index.examples.js +51 -0
  153. package/src/verblets/intent/index.js +72 -0
  154. package/src/verblets/intent/index.spec.js +31 -0
  155. package/src/verblets/number/index.examples.js +33 -0
  156. package/src/verblets/number/index.js +22 -0
  157. package/src/verblets/number/index.spec.js +35 -0
  158. package/src/verblets/number-with-units/index.examples.js +34 -0
  159. package/src/verblets/number-with-units/index.js +19 -0
  160. package/src/verblets/number-with-units/index.spec.js +46 -0
  161. package/src/verblets/schema-org/index.examples.js +56 -0
  162. package/src/verblets/schema-org/index.js +8 -0
  163. package/src/verblets/schema-org/index.spec.js +39 -0
  164. package/src/verblets/to-object/README.md +38 -0
  165. package/src/verblets/to-object/index.examples.js +29 -0
  166. package/src/verblets/to-object/index.js +136 -0
  167. package/src/verblets/to-object/index.spec.js +74 -0
@@ -0,0 +1,333 @@
1
+ import { parse } from 'acorn';
2
+ import * as walk from 'acorn-walk';
3
+ import path from 'node:path';
4
+
5
+ const stripRootDir = (filePath, root = process.cwd()) => {
6
+ return path.resolve('./', filePath).replace(new RegExp(`^${root}`), '');
7
+ };
8
+
9
+ const convertImport = (filePath, importPath) => {
10
+ return stripRootDir(path.resolve(path.dirname(filePath), importPath));
11
+ };
12
+
13
+ const scanFile = (file, code) => {
14
+ const functionsMap = {};
15
+ const functionsSeen = {};
16
+ const variablesMap = {};
17
+ const importsMap = {};
18
+ const exportsMap = {};
19
+
20
+ const comments = [];
21
+
22
+ const ast = parse(code, {
23
+ sourceType: 'module',
24
+ ecmaVersion: 'latest',
25
+ onComment: comments,
26
+ });
27
+
28
+ const commentsMap = comments.reduce((acc, comment) => {
29
+ const commentNew = { ...comment };
30
+ delete commentNew.value;
31
+ return { ...acc, [commentNew.start]: commentNew };
32
+ }, {});
33
+
34
+ walk.simple(ast, {
35
+ ImportDeclaration(importNode) {
36
+ const declaration = importNode.specifiers
37
+ .filter((s) => s.type === 'ImportDefaultSpecifier')
38
+ .map((s) => {
39
+ return s.local.name;
40
+ })?.[0];
41
+ const specifiers = importNode.specifiers
42
+ .filter((s) => s.type === 'ImportSpecifier')
43
+ .map((s) => {
44
+ return { ...s.imported }.name; // also has local name
45
+ });
46
+ const source = importNode.source.value;
47
+ const importKey = source.startsWith('.')
48
+ ? convertImport(file, source)
49
+ : source;
50
+ importsMap[importKey] = {
51
+ start: importNode.start,
52
+ end: importNode.end,
53
+ declaration,
54
+ specifiers,
55
+ source,
56
+ };
57
+ },
58
+
59
+ ExportNamedDeclaration(expNode) {
60
+ if (expNode.source) {
61
+ // Handle re-exports
62
+ const source = expNode.source.value;
63
+ const importKey = source.startsWith('.')
64
+ ? convertImport(file, source)
65
+ : source;
66
+
67
+ // Named exports
68
+ if (expNode.specifiers) {
69
+ expNode.specifiers.forEach((specifier) => {
70
+ if (specifier.type === 'ExportSpecifier') {
71
+ exportsMap[specifier.exported.name ?? specifier.local.name] = {
72
+ start: expNode.start,
73
+ end: expNode.end,
74
+ type: 'NamedExport',
75
+ local: specifier.exported.name ?? specifier.local.name,
76
+ source: importKey,
77
+ };
78
+ importsMap[importKey] = {
79
+ start: expNode.start,
80
+ end: expNode.end,
81
+ declaration: specifier.exported.name ?? specifier.local.name,
82
+ specifiers: [],
83
+ source,
84
+ };
85
+ } else if (specifier.type === 'ExportNamespaceSpecifier') {
86
+ exportsMap[specifier.exported.name ?? specifier.local.name] = {
87
+ start: expNode.start,
88
+ end: expNode.end,
89
+ type: 'NamespaceExport',
90
+ source: importKey,
91
+ };
92
+ importsMap[importKey] = {
93
+ start: expNode.start,
94
+ end: expNode.end,
95
+ declaration: null,
96
+ specifiers: [],
97
+ source,
98
+ };
99
+ }
100
+ });
101
+ }
102
+ } else if (expNode.declaration.type === 'VariableDeclaration') {
103
+ // Handle local exports
104
+ expNode.declaration.declarations.forEach((decNode) => {
105
+ exportsMap[decNode.id.name] = {
106
+ start: expNode.start,
107
+ end: expNode.end,
108
+ type: 'LocalExport',
109
+ local: decNode.id.name,
110
+ };
111
+
112
+ if (decNode.init.type === 'ArrowFunctionExpression') {
113
+ functionsMap[`ArrowFunctionExpression:${decNode.id.name}`] = {
114
+ start: decNode.init.start,
115
+ end: decNode.init.end,
116
+ name: decNode.id.name,
117
+ functionName: `<arrow:${decNode.id.name}>`,
118
+ async: decNode.init.async,
119
+ generator: decNode.init.generator,
120
+ type: 'ArrowFunctionExpression',
121
+ exported: true,
122
+ };
123
+ }
124
+ });
125
+ }
126
+ },
127
+
128
+ ExportAllDeclaration(expNode) {
129
+ if (expNode.source) {
130
+ const source = expNode.source.value;
131
+ const importKey = source.startsWith('.')
132
+ ? convertImport(file, source)
133
+ : source;
134
+ exportsMap[expNode.exported.name] = {
135
+ start: expNode.start,
136
+ end: expNode.end,
137
+ type: 'AllExport',
138
+ local: expNode.exported.name,
139
+ source: importKey,
140
+ };
141
+ importsMap[importKey] = {
142
+ start: expNode.start,
143
+ end: expNode.end,
144
+ declaration: null,
145
+ specifiers: [],
146
+ source,
147
+ };
148
+ }
149
+ },
150
+
151
+ ExportDefaultDeclaration(expNode) {
152
+ if (expNode.declaration.type === 'Identifier') {
153
+ exportsMap.default = {
154
+ type: 'LocalExport',
155
+ local: expNode.declaration.name,
156
+ };
157
+ } else if (expNode.declaration.type === 'ArrowFunctionExpression') {
158
+ exportsMap.default = {
159
+ type: 'DefaultExport',
160
+ start: expNode.start,
161
+ end: expNode.end,
162
+ };
163
+
164
+ functionsMap[`ArrowFunctionExpression:default`] = {
165
+ start: expNode.declaration.start,
166
+ end: expNode.declaration.end,
167
+ functionName: '<default-export>',
168
+ async: expNode.declaration.async,
169
+ generator: expNode.declaration.generator,
170
+ type: 'ArrowFunctionExpression',
171
+ };
172
+ }
173
+ },
174
+
175
+ FunctionDeclaration(fnNode) {
176
+ functionsMap[`${fnNode.type}:${fnNode.id.name}`] = {
177
+ start: fnNode.start,
178
+ end: fnNode.end,
179
+ functionName: `<declaration:${fnNode.id.name}>`,
180
+ name: fnNode.id.name,
181
+ type: fnNode.type,
182
+ async: fnNode.async,
183
+ generator: fnNode.generator,
184
+ exported: false,
185
+ };
186
+ },
187
+
188
+ VariableDeclaration(varNode) {
189
+ const arrowDeclarations = varNode.declarations.filter((d) => {
190
+ return (
191
+ d.id.type === 'Identifier' &&
192
+ d.init?.type === 'ArrowFunctionExpression' &&
193
+ d.init?.body.type === 'BlockStatement'
194
+ );
195
+ });
196
+
197
+ arrowDeclarations.forEach((arrowFnNode) => {
198
+ functionsSeen[
199
+ `${arrowFnNode.init.start}:${arrowFnNode.init.end}`
200
+ ] = true;
201
+
202
+ functionsMap[`ArrowFunctionExpression:${arrowFnNode.id.name}`] = {
203
+ start: arrowFnNode.start,
204
+ end: arrowFnNode.end,
205
+ name: arrowFnNode.id.name,
206
+ functionName: `<arrow:${arrowFnNode.id.name}>`,
207
+ async: arrowFnNode.init.async,
208
+ generator: arrowFnNode.init.generator,
209
+ type: 'ArrowFunctionExpression',
210
+ exported: false,
211
+ };
212
+ });
213
+
214
+ const fnExpDeclarations = varNode.declarations.filter((d) => {
215
+ return (
216
+ d.id.type === 'Identifier' &&
217
+ d.init?.type === 'FunctionExpression' &&
218
+ d.init?.body.type === 'BlockStatement'
219
+ );
220
+ });
221
+
222
+ fnExpDeclarations.forEach((fnExpNode) => {
223
+ functionsSeen[`${fnExpNode.init.start}:${fnExpNode.init.end}`] = true;
224
+
225
+ functionsMap[`FunctionExpression:${fnExpNode.id.name}`] = {
226
+ start: fnExpNode.start,
227
+ end: fnExpNode.end,
228
+ name: fnExpNode.id.name,
229
+ functionName: `<exp:${fnExpNode.id.name}>`,
230
+ async: fnExpNode.init.async,
231
+ generator: fnExpNode.init.generator,
232
+ type: 'FunctionExpression',
233
+ exported: false,
234
+ };
235
+ });
236
+ },
237
+
238
+ // Property Assignment
239
+ AssignmentExpression(node) {
240
+ if (
241
+ (node.right.type === 'FunctionExpression' ||
242
+ node.right.type === 'ArrowFunctionExpression') &&
243
+ node.value
244
+ ) {
245
+ functionsSeen[`${node.right.start}:${node.right.end}`] = true;
246
+
247
+ functionsMap[`${node.value.type}:${node.left.property.name}`] = {
248
+ start: node.start,
249
+ end: node.end,
250
+ name: node.left.property.name,
251
+ functionName: `${node.value.type}:${node.left.property.name}`,
252
+ async: node.right.async,
253
+ generator: node.right.generator,
254
+ type: node.right.type,
255
+ exported: false,
256
+ };
257
+ }
258
+ },
259
+
260
+ // Object Definition
261
+ Property(node) {
262
+ if (
263
+ node.value.type === 'FunctionExpression' ||
264
+ node.value.type === 'ArrowFunctionExpression'
265
+ ) {
266
+ functionsSeen[`${node.value.start}:${node.value.end}`] = true;
267
+
268
+ functionsMap[`Property:${node.key.name}`] = {
269
+ start: node.start,
270
+ end: node.end,
271
+ name: node.key.name,
272
+ functionName: `<property:${node.key.name}>`,
273
+ async: node.value.async,
274
+ generator: node.value.generator,
275
+ type: node.value.type,
276
+ exported: false,
277
+ };
278
+ }
279
+ },
280
+
281
+ // Class Definition
282
+ ClassDeclaration(node) {
283
+ const className = node.id.name;
284
+ node.body.body.forEach((classElement) => {
285
+ if (classElement.type === 'MethodDefinition') {
286
+ functionsSeen[
287
+ `${classElement.value.start}:${classElement.value.end}`
288
+ ] = true;
289
+
290
+ functionsMap[
291
+ `MethodDefinition:${className}.${classElement.key.name}`
292
+ ] = {
293
+ start: node.start,
294
+ end: node.end,
295
+ className: className,
296
+ functionName: `${className}.${classElement.key.name}`,
297
+ name: classElement.key.name,
298
+ async: classElement.value.async,
299
+ generator: classElement.value.generator,
300
+ type: 'MethodDefinition',
301
+ exported: false,
302
+ };
303
+ }
304
+ });
305
+ },
306
+ });
307
+
308
+ walk.simple(ast, {
309
+ FunctionExpression(node) {
310
+ if (!functionsSeen[`${node.start}:${node.end}`]) {
311
+ functionsMap[`${node.type}:${node.start}`] = {
312
+ start: node.start,
313
+ functionName: `<exp>`,
314
+ end: node.end,
315
+ type: node.type,
316
+ async: node.async,
317
+ generator: node.generator,
318
+ };
319
+ }
320
+ },
321
+ });
322
+
323
+ return {
324
+ name: stripRootDir(file),
325
+ functionsMap,
326
+ variablesMap,
327
+ importsMap,
328
+ exportsMap,
329
+ commentsMap,
330
+ };
331
+ };
332
+
333
+ export default scanFile;
@@ -0,0 +1,156 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import parseJSParts from './index.js';
3
+
4
+ const examples = [
5
+ {
6
+ name: 'Single function declaration',
7
+ inputs: {
8
+ file: 'testFile.js',
9
+ code: `
10
+ import testModule from './testModule.js';
11
+
12
+ export function testFunc() {
13
+ return 'abc';
14
+ }
15
+ `,
16
+ },
17
+ want: {
18
+ name: 'testFile.js',
19
+ functionsMap: [
20
+ {
21
+ name: 'testFunc',
22
+ type: 'FunctionDeclaration',
23
+ },
24
+ ],
25
+ importsMap: [
26
+ {
27
+ declaration: 'testModule',
28
+ source: '/testModule.js',
29
+ },
30
+ ],
31
+ },
32
+ },
33
+ {
34
+ name: 'Arrow function declaration',
35
+ inputs: {
36
+ file: 'arrowFuncFile.js',
37
+ code: `
38
+ import arrowModule from './arrowModule.js';
39
+
40
+ export const arrowFunc = () => {
41
+ return 'xyz';
42
+ };
43
+ `,
44
+ },
45
+ want: {
46
+ name: 'arrowFuncFile.js',
47
+ functionsMap: [
48
+ {
49
+ name: 'arrowFunc',
50
+ type: 'ArrowFunctionExpression',
51
+ },
52
+ ],
53
+ importsMap: [
54
+ {
55
+ declaration: 'arrowModule',
56
+ source: '/arrowModule.js',
57
+ },
58
+ ],
59
+ },
60
+ },
61
+ {
62
+ name: 'Class method declaration',
63
+ inputs: {
64
+ file: 'classMethodFile.js',
65
+ code: `
66
+ export class MyClass {
67
+ myMethod() {
68
+ return 'Hello World';
69
+ }
70
+ }
71
+ `,
72
+ },
73
+ want: {
74
+ name: 'classMethodFile.js',
75
+ functionsMap: [
76
+ {
77
+ className: 'MyClass',
78
+ name: 'myMethod',
79
+ type: 'MethodDefinition',
80
+ },
81
+ ],
82
+ importsMap: [],
83
+ },
84
+ },
85
+ {
86
+ name: 'Multiple function declarations',
87
+ inputs: {
88
+ file: 'multipleFunctionsFile.js',
89
+ code: `
90
+ import additionalModule from './additionalModule.js';
91
+
92
+ export function firstFunc() {
93
+ return 'First';
94
+ }
95
+
96
+ export const secondFunc = () => {
97
+ return 'Second';
98
+ };
99
+
100
+ export class ThirdClass {
101
+ thirdFunc() {
102
+ return 'Third';
103
+ }
104
+ }
105
+ `,
106
+ },
107
+ want: {
108
+ name: 'multipleFunctionsFile.js',
109
+ functionsMap: [
110
+ {
111
+ name: 'firstFunc',
112
+ type: 'FunctionDeclaration',
113
+ },
114
+ {
115
+ name: 'secondFunc',
116
+ type: 'ArrowFunctionExpression',
117
+ },
118
+ {
119
+ className: 'ThirdClass',
120
+ name: 'thirdFunc',
121
+ type: 'MethodDefinition',
122
+ },
123
+ ],
124
+ importsMap: [
125
+ {
126
+ declaration: 'additionalModule',
127
+ source: '/additionalModule.js',
128
+ },
129
+ ],
130
+ },
131
+ },
132
+ ];
133
+
134
+ describe('Parse JS parts', () => {
135
+ examples.forEach((example) => {
136
+ it(example.name, () => {
137
+ const result = parseJSParts(example.inputs.file, example.inputs.code);
138
+
139
+ if (example.want.functionsMap) {
140
+ example.want.functionsMap.forEach((fn) => {
141
+ const found =
142
+ result.functionsMap[`${fn.type}:${fn.name}`]?.name ||
143
+ result.functionsMap[`${fn.type}:${fn.className}.${fn.name}`]?.name;
144
+ expect(fn.name).equals(found);
145
+ });
146
+ }
147
+ if (example.want.importsMap) {
148
+ example.want.importsMap.forEach((importDef) => {
149
+ expect(importDef.declaration).equals(
150
+ result.importsMap[`${importDef.source}`]?.declaration
151
+ );
152
+ });
153
+ }
154
+ });
155
+ });
156
+ });
@@ -0,0 +1,39 @@
1
+ const defaultDelimiter = '/';
2
+
3
+ /**
4
+ * Splits sequences using the given delimiter.
5
+ * @param {string[]} sequences - An array of strings.
6
+ * @param {string} delimiter - Delimiter to split the sequences.
7
+ * @returns {string[][]} - An array of split sequences.
8
+ */
9
+ const splitSequences = (sequences, delimiter = defaultDelimiter) =>
10
+ sequences.map((sequence) => sequence.split(delimiter));
11
+
12
+ /**
13
+ * Creates unique sequence tails for the given sequences.
14
+ * @param {string[]} sequences - An array of strings.
15
+ * @param {string} delimiter - Delimiter to split and join the sequences.
16
+ * @returns {Object} - An object mapping the original path to its unique tail.
17
+ */
18
+ export default (sequences, delimiter = defaultDelimiter) => {
19
+ const splitSequencesList = splitSequences(sequences, delimiter);
20
+ const tailsUnique = {};
21
+
22
+ sequences.forEach((sequence, index) => {
23
+ const splitSequence = splitSequencesList[index];
24
+
25
+ for (let i = 1; i <= splitSequence.length; i += 1) {
26
+ const tail = splitSequence.slice(-i).join(delimiter);
27
+ const conflictingSequences = sequences.filter((seq) =>
28
+ seq.endsWith(tail)
29
+ );
30
+
31
+ if (conflictingSequences.length === 1) {
32
+ tailsUnique[sequence] = tail;
33
+ break;
34
+ }
35
+ }
36
+ });
37
+
38
+ return tailsUnique;
39
+ };
@@ -0,0 +1,70 @@
1
+ import * as R from 'ramda';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ import testAdvice from '../../chains/test-advice/index.js';
5
+ import alias from './index.js';
6
+
7
+ const examples = [
8
+ {
9
+ name: 'Basic usage',
10
+ inputs: {
11
+ sequences: ['foo/bar/baz.js', 'foo/biz/baz.js', 'foo/biz/qux.js'],
12
+ },
13
+ want: {
14
+ 'foo/bar/baz.js': 'bar/baz.js',
15
+ 'foo/biz/baz.js': 'biz/baz.js',
16
+ 'foo/biz/qux.js': 'qux.js',
17
+ },
18
+ },
19
+ {
20
+ name: 'Deeper conflict',
21
+ inputs: {
22
+ sequences: ['a/y/x/w/v.js', 'b/y/z/w/v.js'],
23
+ delimiter: '/',
24
+ },
25
+ want: {
26
+ 'a/y/x/w/v.js': 'x/w/v.js',
27
+ 'b/y/z/w/v.js': 'z/w/v.js',
28
+ },
29
+ },
30
+ {
31
+ name: 'No delimiter conflict',
32
+ inputs: {
33
+ sequences: ['192.168.0.1', '192.168.1.1', '10.0.0.1'],
34
+ delimiter: '.',
35
+ },
36
+ want: {
37
+ '192.168.0.1': '168.0.1',
38
+ '192.168.1.1': '1.1',
39
+ '10.0.0.1': '0.0.1',
40
+ },
41
+ },
42
+ ];
43
+
44
+ describe('Path aliases', async () => {
45
+ examples.forEach((example) => {
46
+ it(example.name, () => {
47
+ const got = alias(example.inputs.sequences, example.inputs.delimiter);
48
+ const gotSorted = R.sort(
49
+ ([k1], [k2]) => k1.localeCompare(k2),
50
+ Object.entries(got)
51
+ );
52
+ const wantSorted = R.sort(
53
+ ([k1], [k2]) => k1.localeCompare(k2),
54
+ Object.entries(example.want)
55
+ );
56
+
57
+ expect(gotSorted).toStrictEqual(wantSorted);
58
+ });
59
+ });
60
+ });
61
+
62
+ describe('Path aliases - advice', async () => {
63
+ const advices = await testAdvice('./src/lib/path-aliases/index.js');
64
+
65
+ advices.forEach((a) => {
66
+ it(a.name, () => expect(true).toBe(true));
67
+ });
68
+
69
+ it('Trigger failure', () => expect(advices.length).toBe(0));
70
+ });
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Pave a nested object structure or array based on a dot-separated path.
3
+ * @param {Object|Array} obj - The object or array to modify.
4
+ * @param {string} path - The dot-separated path indicating where to set the value.
5
+ * @param {*} value - The value to set at the specified path.
6
+ */
7
+ export default (obj, path, value) => {
8
+ const pathRe = /^([^.]+(\.[^.]+)*)$/;
9
+
10
+ if (!pathRe.test(path)) {
11
+ throw new Error(`Invalid path: "${path}"`);
12
+ }
13
+
14
+ const keys = path.split('.');
15
+ const objNew = JSON.parse(JSON.stringify(obj));
16
+ let objMutating = objNew;
17
+
18
+ for (let i = 0; i < keys.length; i += 1) {
19
+ const key = Number.isNaN(parseInt(keys[i], 10))
20
+ ? keys[i]
21
+ : parseInt(keys[i], 10);
22
+
23
+ if (i === keys.length - 1) {
24
+ objMutating[key] = value;
25
+ } else {
26
+ if (!objMutating[key]) {
27
+ objMutating[key] = Number.isNaN(parseInt(keys[i + 1], 10)) ? {} : [];
28
+ }
29
+ objMutating = objMutating[key];
30
+ }
31
+ }
32
+
33
+ return objNew;
34
+ };
@@ -0,0 +1,73 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import pave from './index.js';
4
+
5
+ const examples = [
6
+ {
7
+ name: 'Set a nested object value',
8
+ inputs: { obj: {}, path: 'a.b.c', value: 42 },
9
+ want: { result: { a: { b: { c: 42 } } } },
10
+ },
11
+ {
12
+ name: 'Set a nested array value',
13
+ inputs: { obj: [], path: '0.1.2', value: 42 },
14
+ want: { result: [[undefined, [undefined, undefined, 42]]] },
15
+ },
16
+ {
17
+ name: 'Set a mixed object and array value',
18
+ inputs: { obj: {}, path: 'a.0.b', value: 42 },
19
+ want: { result: { a: [{ b: 42 }] } },
20
+ },
21
+ {
22
+ name: 'Set a value on an existing object',
23
+ inputs: { obj: { x: { y: 1 } }, path: 'x.z', value: 2 },
24
+ want: { result: { x: { y: 1, z: 2 } } },
25
+ },
26
+ {
27
+ name: 'Set a value on an existing array',
28
+ inputs: { obj: [0, [1]], path: '1.2', value: 3 },
29
+ want: { result: [0, [1, undefined, 3]] },
30
+ },
31
+ {
32
+ name: 'Override an existing value in an object',
33
+ inputs: { obj: { a: { b: 1 } }, path: 'a.b', value: 99 },
34
+ want: { result: { a: { b: 99 } } },
35
+ },
36
+ {
37
+ name: 'Override an existing value in an array',
38
+ inputs: { obj: [0, [1, 2]], path: '1.1', value: 99 },
39
+ want: { result: [0, [1, 99]] },
40
+ },
41
+ {
42
+ name: 'Set a value with an empty path (throws)',
43
+ inputs: { obj: { x: 1 }, path: '', value: 42 },
44
+ want: { throws: true },
45
+ },
46
+ {
47
+ name: 'Set a value with an invalid path (throws)',
48
+ inputs: { obj: { x: 1 }, path: '.', value: 42 },
49
+ want: { throws: true },
50
+ },
51
+ {
52
+ name: 'Set a value with a single element path on an object',
53
+ inputs: { obj: {}, path: 'a', value: 42 },
54
+ want: { result: { a: 42 } },
55
+ },
56
+ ];
57
+
58
+ describe('pave', () => {
59
+ examples.forEach((example) => {
60
+ it(example.name, () => {
61
+ const { obj } = example.inputs;
62
+
63
+ if (example.want.throws) {
64
+ expect(() =>
65
+ pave(obj, example.inputs.path, example.inputs.value)
66
+ ).toThrow();
67
+ } else {
68
+ const result = pave(obj, example.inputs.path, example.inputs.value);
69
+ expect(result).toEqual(example.want.result);
70
+ }
71
+ });
72
+ });
73
+ });