data-structure-typed 1.53.2 → 1.53.4

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,215 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import * as ts from 'typescript';
4
+ import { toPascalCase } from './test/utils';
5
+
6
+ const isReplaceMD = false;
7
+ const START_MARKER = '[//]: # (No deletion!!! Start of Example Replace Section)';
8
+ const END_MARKER = '[//]: # (No deletion!!! End of Example Replace Section)';
9
+
10
+ /**
11
+ * Recursively retrieve all `.ts` files in a directory.
12
+ */
13
+ function getAllTestFiles(dir: string): string[] {
14
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
15
+
16
+ const files = entries
17
+ .filter(file => !file.isDirectory() && file.name.endsWith('.ts'))
18
+ .map(file => path.join(dir, file.name));
19
+
20
+ const directories = entries.filter(entry => entry.isDirectory());
21
+
22
+ for (const directory of directories) {
23
+ files.push(...getAllTestFiles(path.join(dir, directory.name)));
24
+ }
25
+
26
+ return files;
27
+ }
28
+
29
+ /**
30
+ * Extract test cases with `@example` from TypeScript files using AST.
31
+ */
32
+ function extractExamplesFromFile(filePath: string): { name: string; body: string }[] {
33
+ const fileContent = fs.readFileSync(filePath, 'utf-8');
34
+ const sourceFile = ts.createSourceFile(filePath, fileContent, ts.ScriptTarget.Latest, true);
35
+
36
+ const examples: { name: string; body: string }[] = [];
37
+
38
+ function visit(node: ts.Node) {
39
+ if (
40
+ ts.isCallExpression(node) && // Ensure it's a function call
41
+ node.arguments.length >= 2 && // At least two arguments
42
+ ts.isStringLiteral(node.arguments[0]) && // First argument is a string
43
+ node.arguments[0].text.startsWith('@example') && // Matches @example
44
+ ts.isArrowFunction(node.arguments[1]) // Second argument is an arrow function
45
+ ) {
46
+ const exampleName = node.arguments[0].text.replace('@example ', '').trim();
47
+ const bodyNode = node.arguments[1].body;
48
+
49
+ let exampleBody: string;
50
+ if (ts.isBlock(bodyNode)) {
51
+ // If it's a block, remove outer {}
52
+ exampleBody = bodyNode.statements
53
+ .map(stmt => stmt.getFullText(sourceFile))
54
+ .join('')
55
+ .trim();
56
+ } else {
57
+ // If it's a single expression, use it directly
58
+ exampleBody = bodyNode.getFullText(sourceFile).trim();
59
+ }
60
+
61
+ const transformedBody = exampleBody
62
+ .replace(
63
+ /expect\((.*?)\)\.(toBeUndefined|toBeNull)\(\);/g,
64
+ (match, actual, method) => {
65
+ const expectedValue = method === 'toBeUndefined' ? 'undefined' : 'null';
66
+ return `console.log(${actual}); // ${expectedValue}`;
67
+ }
68
+ )
69
+ .replace(
70
+ /expect\((.*?)\)\.(toEqual|toBe|toStrictEqual|toHaveLength|toMatchObject)\((.*?)\);/gs, // Use `s` flag for multiline
71
+ (match, actual, method, expected) => {
72
+ expected = expected.replace(/\n/g, '\n //')
73
+ return `console.log(${actual}); // ${expected}`;
74
+ }
75
+ )
76
+ .trim();
77
+
78
+ examples.push({ name: exampleName, body: transformedBody });
79
+ }
80
+
81
+ ts.forEachChild(node, visit);
82
+ }
83
+
84
+ visit(sourceFile);
85
+
86
+ return examples;
87
+ }
88
+
89
+ /**
90
+ * Add examples to the corresponding class in the source file.
91
+ */
92
+ function addExamplesToSourceFile(
93
+ sourceFilePath: string,
94
+ className: string,
95
+ examples: { name: string; body: string }[]
96
+ ): void {
97
+ if (!fs.existsSync(sourceFilePath)) {
98
+ console.warn(`Source file not found: ${sourceFilePath}`);
99
+ return;
100
+ }
101
+
102
+ const sourceContent = fs.readFileSync(sourceFilePath, 'utf-8');
103
+ const sourceFile = ts.createSourceFile(sourceFilePath, sourceContent, ts.ScriptTarget.Latest, true);
104
+
105
+ let updatedContent = sourceContent;
106
+
107
+ const classNode = sourceFile.statements.find(
108
+ stmt => ts.isClassDeclaration(stmt) && stmt.name?.text === className
109
+ ) as ts.ClassDeclaration | undefined;
110
+
111
+ if (classNode) {
112
+ const classStart = classNode.getStart(sourceFile);
113
+ const classEnd = classNode.getEnd();
114
+ const classText = classNode.getFullText(sourceFile);
115
+
116
+ // Extract annotation content
117
+ const existingCommentMatch = classText.match(/\/\*\*([\s\S]*?)\*\//);
118
+ if (!existingCommentMatch) {
119
+ console.warn(`No existing comment found for class: ${className}`);
120
+ return;
121
+ }
122
+
123
+ const existingCommentInner = existingCommentMatch[1].replace(/^\n \* /, ''); // Extract comment content (excluding `/**` and `*/`)
124
+
125
+ // Replace @example part
126
+ const exampleSection = examples
127
+ .map(
128
+ example =>
129
+ ` * @example \n * \/\/ ${example.name} \n${example.body
130
+ .split('\n')
131
+ .map(line => ` * ${line}`)
132
+ .join('\n')}`
133
+ )
134
+ .join('\n') + '\n ';
135
+
136
+ let newComment = '';
137
+ if (existingCommentInner.includes('@example')) {
138
+ newComment = existingCommentInner.replace(/ \* @example[\s\S]*?(?=\*\/|$)/g, exampleSection);
139
+ } else {
140
+ newComment = existingCommentInner + `${exampleSection}`;
141
+ }
142
+
143
+
144
+ // Replace original content
145
+ updatedContent =
146
+ sourceContent.slice(0, classStart - existingCommentInner.length - 3) +
147
+ newComment +
148
+ classText.slice(existingCommentMatch[0].length).trim() +
149
+ sourceContent.slice(classEnd);
150
+ }
151
+
152
+ fs.writeFileSync(sourceFilePath, updatedContent, 'utf-8');
153
+ console.log(`Updated examples in ${sourceFilePath}`);
154
+ }
155
+
156
+
157
+ /**
158
+ * Process all test files and update README.md and source files.
159
+ */
160
+ function updateExamples(testDir: string, readmePath: string, sourceBaseDir: string): void {
161
+ const testFiles = getAllTestFiles(testDir);
162
+
163
+ let allExamples: string[] = [];
164
+ for (const file of testFiles) {
165
+ const examples = extractExamplesFromFile(file);
166
+
167
+ if (examples.length === 0) {
168
+ console.log(`No @example found in test file: ${file}`);
169
+ continue;
170
+ }
171
+
172
+ const relativePath = path.relative(testDir, file);
173
+ const sourceFilePath = path.resolve(sourceBaseDir, relativePath.replace('.test.ts', '.ts'));
174
+ const className = path.basename(sourceFilePath, '.ts');
175
+
176
+ addExamplesToSourceFile(sourceFilePath, toPascalCase(className), examples);
177
+
178
+ allExamples = allExamples.concat(
179
+ examples.map(example => `### ${example.name}\n\`\`\`typescript\n${example.body}\n\`\`\``)
180
+ );
181
+ }
182
+
183
+ if (isReplaceMD && allExamples.length > 0) {
184
+ replaceExamplesInReadme(readmePath, allExamples);
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Replace content between markers in README.md.
190
+ */
191
+ function replaceExamplesInReadme(readmePath: string, newExamples: string[]): void {
192
+ const readmeContent = fs.readFileSync(readmePath, 'utf-8');
193
+
194
+ const startIdx = readmeContent.indexOf(START_MARKER);
195
+ const endIdx = readmeContent.indexOf(END_MARKER);
196
+
197
+ if (startIdx === -1 || endIdx === -1) {
198
+ throw new Error(`Markers not found in ${readmePath}`);
199
+ }
200
+
201
+ const before = readmeContent.slice(0, startIdx + START_MARKER.length);
202
+ const after = readmeContent.slice(endIdx);
203
+
204
+ const updatedContent = `${before}\n\n${newExamples.join('\n\n')}\n\n${after}`;
205
+ fs.writeFileSync(readmePath, updatedContent, 'utf-8');
206
+
207
+ console.log(`README.md updated with new examples.`);
208
+ }
209
+
210
+ // Run the script
211
+ const testDir = path.resolve(__dirname, 'test/unit');
212
+ const readmePath = path.resolve(__dirname, 'README.md');
213
+ const sourceBaseDir = path.resolve(__dirname, 'src');
214
+
215
+ updateExamples(testDir, readmePath, sourceBaseDir);