@postxl/generator 1.4.0 → 1.4.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.
@@ -65,6 +65,8 @@ export type CustomBlock = {
65
65
  anchorBefore: string[];
66
66
  /** Anchor context: non-empty lines after the block for positioning */
67
67
  anchorAfter: string[];
68
+ /** Name of the enclosing class member/method where this block was extracted */
69
+ enclosingMemberName?: string;
68
70
  };
69
71
  /**
70
72
  * Result of extracting custom blocks from source code
@@ -119,14 +119,17 @@ function processEndMarker(line, lineIndex, endMatch, currentBlock, lines, blocks
119
119
  // Capture anchor context
120
120
  const anchorBefore = captureAnchorContext(lines, currentBlock.startLineIndex, 'before');
121
121
  const anchorAfter = captureAnchorContext(lines, lineIndex, 'after');
122
- blocks.push({
122
+ const enclosingMemberName = findEnclosingMemberName(lines, currentBlock.startLineIndex);
123
+ const block = {
123
124
  name: currentBlock.name,
124
125
  lines: currentBlock.lines,
125
126
  startLineIndex: currentBlock.startLineIndex,
126
127
  endLineIndex: lineIndex,
127
128
  anchorBefore,
128
129
  anchorAfter,
129
- });
130
+ ...(enclosingMemberName ? { enclosingMemberName } : {}),
131
+ };
132
+ blocks.push(block);
130
133
  return null;
131
134
  }
132
135
  /**
@@ -246,6 +249,147 @@ function isSignificantLine(line) {
246
249
  function normalizeAnchorLine(line) {
247
250
  return line.trim();
248
251
  }
252
+ const METHOD_SIGNATURE_PATTERN = /^\s*(?:public|private|protected|static|readonly|async|\s)*([A-Za-z_$][\w$]*)\s*(?:<[^>{}]*>)?\s*\(/;
253
+ const NON_MEMBER_KEYWORDS = new Set([
254
+ 'if',
255
+ 'for',
256
+ 'while',
257
+ 'switch',
258
+ 'do',
259
+ 'return',
260
+ 'const',
261
+ 'let',
262
+ 'var',
263
+ 'new',
264
+ 'class',
265
+ 'function',
266
+ 'typeof',
267
+ 'instanceof',
268
+ ]);
269
+ function findMemberNameInLine(line) {
270
+ const trimmed = line.trim();
271
+ if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) {
272
+ return undefined;
273
+ }
274
+ const signatureMatch = METHOD_SIGNATURE_PATTERN.exec(line);
275
+ if (!signatureMatch) {
276
+ return undefined;
277
+ }
278
+ const memberName = signatureMatch[1];
279
+ if (!memberName || NON_MEMBER_KEYWORDS.has(memberName)) {
280
+ return undefined;
281
+ }
282
+ return memberName;
283
+ }
284
+ function findEnclosingMemberName(lines, blockStartLineIndex) {
285
+ for (let i = blockStartLineIndex - 1; i >= 0; i--) {
286
+ const line = lines[i];
287
+ if (line === undefined) {
288
+ continue;
289
+ }
290
+ const memberName = findMemberNameInLine(line);
291
+ if (memberName) {
292
+ return memberName;
293
+ }
294
+ }
295
+ return undefined;
296
+ }
297
+ function inferMemberNameFromBlockName(blockName) {
298
+ if (!blockName) {
299
+ return undefined;
300
+ }
301
+ const separatorMatch = /[_-]([A-Za-z_$][\w$]*)$/.exec(blockName);
302
+ if (!separatorMatch) {
303
+ return undefined;
304
+ }
305
+ return separatorMatch[1];
306
+ }
307
+ function countChar(line, char) {
308
+ let count = 0;
309
+ let inSingleQuote = false;
310
+ let inDoubleQuote = false;
311
+ let inTemplate = false;
312
+ let isEscaped = false;
313
+ for (const lineChar of line) {
314
+ if (isEscaped) {
315
+ isEscaped = false;
316
+ continue;
317
+ }
318
+ if (lineChar === '\\') {
319
+ isEscaped = true;
320
+ continue;
321
+ }
322
+ if (!inDoubleQuote && !inTemplate && lineChar === "'") {
323
+ inSingleQuote = !inSingleQuote;
324
+ continue;
325
+ }
326
+ if (!inSingleQuote && !inTemplate && lineChar === '"') {
327
+ inDoubleQuote = !inDoubleQuote;
328
+ continue;
329
+ }
330
+ if (!inSingleQuote && !inDoubleQuote && lineChar === '`') {
331
+ inTemplate = !inTemplate;
332
+ continue;
333
+ }
334
+ if (inSingleQuote || inDoubleQuote || inTemplate) {
335
+ continue;
336
+ }
337
+ if (lineChar === char) {
338
+ count++;
339
+ }
340
+ }
341
+ return count;
342
+ }
343
+ function findMemberRange(targetLines, memberName) {
344
+ for (let i = 0; i < targetLines.length; i++) {
345
+ const line = targetLines[i];
346
+ if (line === undefined) {
347
+ continue;
348
+ }
349
+ if (findMemberNameInLine(line) !== memberName) {
350
+ continue;
351
+ }
352
+ let j = i;
353
+ let foundOpeningBrace = line.includes('{');
354
+ while (!foundOpeningBrace && j + 1 < targetLines.length) {
355
+ j++;
356
+ const continuationLine = targetLines[j];
357
+ if (continuationLine?.includes('{')) {
358
+ foundOpeningBrace = true;
359
+ }
360
+ if (continuationLine?.trim().endsWith(';')) {
361
+ break;
362
+ }
363
+ }
364
+ if (!foundOpeningBrace) {
365
+ continue;
366
+ }
367
+ let braceDepth = 0;
368
+ for (let k = i; k < targetLines.length; k++) {
369
+ const memberLine = targetLines[k];
370
+ if (memberLine === undefined) {
371
+ continue;
372
+ }
373
+ braceDepth += countChar(memberLine, '{');
374
+ braceDepth -= countChar(memberLine, '}');
375
+ if (braceDepth === 0 && k > i) {
376
+ return { start: i, end: k + 1 };
377
+ }
378
+ }
379
+ }
380
+ return null;
381
+ }
382
+ function findInsertionPositionByAnchors(block, targetLines) {
383
+ const beforePosition = findAnchorSequence(block.anchorBefore, targetLines, 'before');
384
+ if (beforePosition !== null) {
385
+ return beforePosition + 1;
386
+ }
387
+ const afterPosition = findAnchorSequence(block.anchorAfter, targetLines, 'after');
388
+ if (afterPosition !== null) {
389
+ return afterPosition;
390
+ }
391
+ return findSingleAnchorMatch(block, targetLines);
392
+ }
249
393
  /**
250
394
  * Finds the best position to insert a custom block in the target content
251
395
  * based on anchor context matching.
@@ -255,24 +399,21 @@ function normalizeAnchorLine(line) {
255
399
  * @returns The line index where the block should be inserted, or null if no good position found
256
400
  */
257
401
  function findInsertionPosition(block, targetLines) {
258
- // Strategy 1: Try to find a match using "before" anchors
259
- // We look for the anchor sequence and insert after the last matched anchor
260
- const beforePosition = findAnchorSequence(block.anchorBefore, targetLines, 'before');
261
- if (beforePosition !== null) {
262
- return beforePosition + 1; // Insert after the anchor
263
- }
264
- // Strategy 2: Try to find a match using "after" anchors
265
- // We look for the anchor sequence and insert before the first matched anchor
266
- const afterPosition = findAnchorSequence(block.anchorAfter, targetLines, 'after');
267
- if (afterPosition !== null) {
268
- return afterPosition; // Insert before the anchor
269
- }
270
- // Strategy 3: Try to find any single anchor match
271
- const singleAnchor = findSingleAnchorMatch(block, targetLines);
272
- if (singleAnchor !== null) {
273
- return singleAnchor;
402
+ const candidateMemberNames = [
403
+ ...new Set([block.enclosingMemberName, inferMemberNameFromBlockName(block.name)].filter((name) => Boolean(name))),
404
+ ];
405
+ for (const memberName of candidateMemberNames) {
406
+ const range = findMemberRange(targetLines, memberName);
407
+ if (!range) {
408
+ continue;
409
+ }
410
+ const scopedLines = targetLines.slice(range.start, range.end);
411
+ const scopedPosition = findInsertionPositionByAnchors(block, scopedLines);
412
+ if (scopedPosition !== null) {
413
+ return range.start + scopedPosition;
414
+ }
274
415
  }
275
- return null;
416
+ return findInsertionPositionByAnchors(block, targetLines);
276
417
  }
277
418
  /**
278
419
  * Finds a sequence of anchor lines in the target content
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postxl/generator",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Core package that orchestrates the code generation of a PXL project",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -46,7 +46,7 @@
46
46
  "jszip": "3.10.1",
47
47
  "minimatch": "^10.2.2",
48
48
  "p-limit": "3.1.0",
49
- "@postxl/schema": "^1.9.0",
49
+ "@postxl/schema": "^1.10.1",
50
50
  "@postxl/utils": "^1.4.0"
51
51
  },
52
52
  "devDependencies": {