Skip to content

Commit 8e6117b

Browse files
committed
chore,fix: correct example extraction script, remove test warnings, and standardize subpackage publishing
- Fix a non-idempotent bug in the example extraction script that caused JSDoc content to be duplicated when regenerating examples from test cases - Ensure existing JSDoc blocks are replaced using accurate AST comment boundaries instead of brittle string offset calculations - Eliminate test-time warnings produced during example extraction and transformation - Standardize sub-package publishing conventions across the monorepo to ensure consistent build, versioning, and release behavior
1 parent b03908e commit 8e6117b

File tree

4 files changed

+61
-45
lines changed

4 files changed

+61
-45
lines changed

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
22
"name": "data-structure-typed",
3-
"version": "2.1.1",
3+
"version": "2.1.2",
44
"description": "Standard data structure",
55
"browser": "dist/umd/data-structure-typed.min.js",
66
"umd:main": "dist/umd/data-structure-typed.min.js",
77
"main": "dist/cjs/index.cjs",
8-
"module": "./dist/esm/index.mjs",
8+
"module": "dist/esm/index.mjs",
99
"types": "dist/types/index.d.ts",
1010
"exports": {
1111
".": {
@@ -27,7 +27,7 @@
2727
"build:ecu": "npm run build:node && npm run build:types && npm run build:umd",
2828
"build:docs": "npm run gen:examples && typedoc --out docs ./src",
2929
"build:docs-class": "npm run gen:examples && typedoc --out docs ./src/data-structures",
30-
"gen:examples": "ts-node scripts/testToExample.ts",
30+
"gen:examples": "ts-node scripts/test-to-example.ts",
3131
"test:in-band": "jest --runInBand",
3232
"test": "npm run test:in-band",
3333
"test:integration": "npm run update:subs && jest --config jest.integration.config.js && tsc test/integration/compile.test.ts && node test/integration/compile.mjs",
Lines changed: 56 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -151,62 +151,77 @@ function addExamplesToSourceFile(
151151
const sourceContent = fs.readFileSync(sourceFilePath, 'utf-8');
152152
const sourceFile = ts.createSourceFile(sourceFilePath, sourceContent, ts.ScriptTarget.Latest, true);
153153

154-
let updatedContent = sourceContent;
155-
156154
const classNode = sourceFile.statements.find(
157155
stmt => ts.isClassDeclaration(stmt) && stmt.name?.text === className
158156
) as ts.ClassDeclaration | undefined;
159157

160-
if (classNode) {
161-
const classStart = classNode.getStart(sourceFile);
162-
const classEnd = classNode.getEnd();
163-
const classText = classNode.getFullText(sourceFile);
158+
if (!classNode) return;
164159

165-
// Extract annotation content
166-
const existingCommentMatch = classText.match(/\/\*\*([\s\S]*?)\*\//);
167-
if (!existingCommentMatch) {
168-
console.warn(`No existing comment found for class: ${className}`);
169-
return;
170-
}
160+
// getFullStart() includes leading comments/trivia
161+
const fullStart = classNode.getFullStart();
162+
const classStart = classNode.getStart(sourceFile); // usually points at `export class ...`
171163

172-
const existingCommentInner = existingCommentMatch[1].replace(/^\n \* /, ''); // Extract comment content (excluding `/**` and `*/`)
173-
174-
// Replace @example part
175-
const exampleSection = examples
176-
.map(
177-
example => {
178-
const indentedBody = ' ' + example.body;
179-
return ` * @example\n * \/\/ ${example.name}\n${indentedBody
180-
.split('\n')
181-
.map(line => {
182-
if (line.trim() === '') return ` *`
183-
return ` * ${line}`})
184-
.join('\n')}`
185-
}
186-
)
187-
.join('\n') + '\n ';
188-
189-
let newComment = '';
190-
if (existingCommentInner.includes('@example')) {
191-
newComment = existingCommentInner.replace(/ \* @example[\s\S]*?(?=\*\/|$)/g, exampleSection);
192-
} else {
193-
newComment = existingCommentInner + `${exampleSection.trimStart()}`;
194-
}
164+
// Search only in the leading trivia region for the nearest JSDoc block
165+
const leadingRegion = sourceContent.slice(fullStart, classStart);
166+
const commentStartInLeading = leadingRegion.lastIndexOf('/**');
167+
if (commentStartInLeading === -1) {
168+
console.warn(`No existing comment found for class: ${className}`);
169+
return;
170+
}
171+
const commentStart = fullStart + commentStartInLeading;
172+
173+
const commentEnd = sourceContent.indexOf('*/', commentStart);
174+
if (commentEnd === -1 || commentEnd > classStart) {
175+
console.warn(`Malformed comment for class: ${className}`);
176+
return;
177+
}
178+
const commentEndInclusive = commentEnd + 2;
179+
180+
const existingCommentBlock = sourceContent.slice(commentStart, commentEndInclusive);
181+
const existingCommentMatch = existingCommentBlock.match(/\/\*\*([\s\S]*?)\*\//);
182+
if (!existingCommentMatch) {
183+
console.warn(`No existing comment found for class: ${className}`);
184+
return;
185+
}
195186

187+
const existingCommentInner = existingCommentMatch[1]; // keep inner as-is, including leading newline
196188

197-
// Replace original content
198-
updatedContent =
199-
sourceContent.slice(0, classStart - existingCommentInner.length - 3) +
200-
newComment +
201-
classText.slice(existingCommentMatch[0].length).trim() +
202-
sourceContent.slice(classEnd);
189+
const exampleSection =
190+
examples
191+
.map(example => {
192+
const indentedBody = ' ' + example.body;
193+
return ` * @example\n * \/\/ ${example.name}\n${indentedBody
194+
.split('\n')
195+
.map(line => (line.trim() === '' ? ` *` : ` * ${line}`))
196+
.join('\n')}`;
197+
})
198+
.join('\n') + '\n';
199+
200+
let newInner: string;
201+
if (existingCommentInner.includes('@example')) {
202+
// Replace from the first " * @example" to the end of the block (before */)
203+
newInner = existingCommentInner.replace(/^\s*\*\s@example[\s\S]*$/m, exampleSection.trimEnd());
204+
// Important: the regex above may not catch if @example is not at line start exactly with spaces.
205+
// A safer approach:
206+
newInner = existingCommentInner.replace(/ \* @example[\s\S]*?(?=\*\/|$)/g, exampleSection);
207+
} else {
208+
newInner = existingCommentInner.replace(/\s*$/, '\n') + exampleSection;
203209
}
204210

211+
// Rebuild full comment block
212+
const newCommentBlock = `/**${newInner.replace(/^\n?/, '\n').replace(/\s*$/, '\n')} */`;
213+
214+
const updatedContent =
215+
sourceContent.slice(0, commentStart) +
216+
newCommentBlock +
217+
sourceContent.slice(commentEndInclusive);
218+
205219
fs.writeFileSync(sourceFilePath, updatedContent, 'utf-8');
206220
console.log(`Updated examples in ${sourceFilePath}`);
207221
}
208222

209223

224+
210225
/**
211226
* Process all test files and update README.md and source files.
212227
*/

src/data-structures/binary-tree/red-black-tree.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export class RedBlackTreeNode<K = any, V = any> extends BSTNode<K, V> {
8989
}
9090

9191
/**
92-
* RRRRRRRRRRRepresents a Red-Black Tree (self-balancing BST) supporting map-like mode and stable O(log n) updates.
92+
* Represents a Red-Black Tree (self-balancing BST) supporting map-like mode and stable O(log n) updates.
9393
* @remarks Time O(1), Space O(1)
9494
* @template K
9595
* @template V

tsconfig.test.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"extends": "./tsconfig.json",
33
"compilerOptions": {
4+
"isolatedModules": true,
45
"noEmit": true,
56
"types": ["node", "jest"]
67
},

0 commit comments

Comments
 (0)