zemdomu 1.3.7 → 1.3.9

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/out/cli.js CHANGED
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- const glob_1 = __importDefault(require("glob"));
7
+ const glob_1 = require("glob");
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const project_linter_1 = require("./project-linter");
10
10
  function parsePatterns(inputs) {
@@ -66,7 +66,7 @@ async function run() {
66
66
  }
67
67
  const files = new Set();
68
68
  for (const pattern of patterns) {
69
- const matches = glob_1.default.sync(pattern, { nodir: true });
69
+ const matches = (0, glob_1.globSync)(pattern, { nodir: true });
70
70
  for (const m of matches)
71
71
  files.add(m);
72
72
  }
@@ -63,7 +63,6 @@ export declare class ComponentAnalyzer {
63
63
  private getRuleType;
64
64
  analyzeComponentTree(): LintResult[];
65
65
  private findCrossComponentH1Issues;
66
- private findReferenceForComp;
67
66
  /**
68
67
  * Improved implementation to find heading order issues across components
69
68
  */
@@ -318,61 +318,105 @@ class ComponentAnalyzer {
318
318
  return results;
319
319
  }
320
320
  findCrossComponentH1Issues(results) {
321
- var _a;
321
+ var _a, _b;
322
322
  const entryPoints = this.findEntryPoints();
323
+ const emitted = new Set();
324
+ const getDisplayName = (component) => {
325
+ if (component.name)
326
+ return component.name;
327
+ return path.basename(component.filePath, path.extname(component.filePath));
328
+ };
329
+ const addResult = (result) => {
330
+ const key = `${result.rule}|${result.filePath}|${result.line}|${result.column}|${result.message}`;
331
+ if (emitted.has(key))
332
+ return;
333
+ emitted.add(key);
334
+ results.push(result);
335
+ };
323
336
  for (const entry of entryPoints) {
324
337
  const comps = this.findComponentsWithRule(entry, 'singleH1', 0);
325
- if (comps.length > 1) {
326
- for (let i = 1; i < comps.length; i++) {
327
- const comp = comps[i];
328
- if (!comp || !comp.name) {
329
- console.error('[ZemDomu] Missing component or name during cross-component analysis', comp);
338
+ if (comps.length <= 1)
339
+ continue;
340
+ const conflictMap = new Map();
341
+ for (const comp of comps) {
342
+ const conflicts = comps
343
+ .filter(other => other.filePath !== comp.filePath)
344
+ .map(getDisplayName);
345
+ if (conflicts.length)
346
+ conflictMap.set(comp.filePath, conflicts);
347
+ }
348
+ const usageMap = new Map();
349
+ const usageStack = new Set();
350
+ const collectUsage = (component, depth = 0) => {
351
+ if (this.maxDepth !== undefined && depth > this.maxDepth)
352
+ return;
353
+ if (usageStack.has(component.filePath))
354
+ return;
355
+ usageStack.add(component.filePath);
356
+ for (const ref of component.usesComponents) {
357
+ if (!ref.path || !this.componentRegistry.has(ref.path))
330
358
  continue;
331
- }
332
- const ref = this.findReferenceForComp(entry, comp.filePath, 0);
333
- if (ref) {
334
- // Use first JSX usage location instead of import location
335
- const location = ref.usageLocations[0] || ref.sourceLocation;
336
- results.push({
337
- filePath: entry.filePath,
338
- line: location.line,
339
- column: location.column,
340
- message: `Multiple <h1> tags: component '${comp.name}' brings an extra <h1>. Use a lower-level heading.`,
341
- rule: 'singleH1'
359
+ const child = this.componentRegistry.get(ref.path);
360
+ if (!usageMap.has(child.filePath))
361
+ usageMap.set(child.filePath, []);
362
+ const locations = ref.usageLocations.length > 0 ? ref.usageLocations : [ref.sourceLocation];
363
+ for (const loc of locations) {
364
+ usageMap.get(child.filePath).push({
365
+ parent: component,
366
+ location: { filePath: component.filePath, line: loc.line, column: loc.column },
342
367
  });
343
368
  }
344
- else {
345
- const issue = (_a = comp.issues.get('singleH1')) === null || _a === void 0 ? void 0 : _a[0];
346
- if (issue) {
347
- results.push({
348
- filePath: comp.filePath,
349
- line: issue.line,
350
- column: issue.column,
351
- message: `Multiple <h1> across components - consider using lower-level headings.`,
352
- rule: 'singleH1'
353
- });
354
- }
355
- }
369
+ collectUsage(child, depth + 1);
370
+ }
371
+ usageStack.delete(component.filePath);
372
+ };
373
+ collectUsage(entry, 0);
374
+ for (const comp of comps) {
375
+ const conflicts = conflictMap.get(comp.filePath);
376
+ if (!conflicts || !conflicts.length)
377
+ continue;
378
+ const compName = getDisplayName(comp);
379
+ const issues = (_a = comp.issues.get('singleH1')) !== null && _a !== void 0 ? _a : [];
380
+ if (!issues.length)
381
+ continue;
382
+ const conflictText = conflicts.map(name => `'${name}'`).join(', ');
383
+ const usageEntries = (_b = usageMap.get(comp.filePath)) !== null && _b !== void 0 ? _b : [];
384
+ const usageRelated = usageEntries.map(u => ({
385
+ filePath: u.location.filePath,
386
+ line: u.location.line,
387
+ column: u.location.column,
388
+ message: `Rendered via '${getDisplayName(u.parent)}'`,
389
+ }));
390
+ for (const issue of issues) {
391
+ addResult({
392
+ filePath: comp.filePath,
393
+ line: issue.line,
394
+ column: issue.column,
395
+ message: `Multiple <h1> tags across components. This <h1> in '${compName}' conflicts with ${conflictText}.`,
396
+ rule: 'singleH1',
397
+ related: usageRelated,
398
+ });
399
+ }
400
+ const childIssueLocations = issues.map(issue => ({
401
+ filePath: comp.filePath,
402
+ line: issue.line,
403
+ column: issue.column,
404
+ message: `Defined in '${compName}'`,
405
+ }));
406
+ for (const usage of usageEntries) {
407
+ const parentName = getDisplayName(usage.parent);
408
+ addResult({
409
+ filePath: usage.location.filePath,
410
+ line: usage.location.line,
411
+ column: usage.location.column,
412
+ message: `Component '${compName}' renders an extra <h1> that conflicts with ${conflictText}.`,
413
+ rule: 'singleH1',
414
+ related: childIssueLocations.length ? childIssueLocations : undefined,
415
+ });
356
416
  }
357
417
  }
358
418
  }
359
419
  }
360
- findReferenceForComp(root, targetPath, depth = 0) {
361
- if (this.maxDepth !== undefined && depth > this.maxDepth)
362
- return null;
363
- for (const ref of root.usesComponents) {
364
- if (ref.path === targetPath)
365
- return ref;
366
- }
367
- for (const ref of root.usesComponents) {
368
- if (ref.path && this.componentRegistry.has(ref.path)) {
369
- const nested = this.findReferenceForComp(this.componentRegistry.get(ref.path), targetPath, depth + 1);
370
- if (nested)
371
- return ref;
372
- }
373
- }
374
- return null;
375
- }
376
420
  /**
377
421
  * Improved implementation to find heading order issues across components
378
422
  */
@@ -389,7 +433,7 @@ class ComponentAnalyzer {
389
433
  * and checks for heading level issues
390
434
  */
391
435
  analyzeHeadingHierarchy(component, results, depth = 0) {
392
- var _a, _b, _c, _d, _e, _f;
436
+ var _a, _b, _c, _d;
393
437
  if (this.maxDepth !== undefined && depth > this.maxDepth)
394
438
  return;
395
439
  if (this.processingComponentStack.has(component.filePath)) {
@@ -404,21 +448,44 @@ class ComponentAnalyzer {
404
448
  for (const heading of allHeadings) {
405
449
  if (lastLevel > 0) {
406
450
  if (heading.heading.level > lastLevel + 1) {
407
- // We found a heading level skip
451
+ const locationFile = heading.heading.filePath;
452
+ const locationLine = heading.heading.line;
453
+ const locationColumn = heading.heading.column;
454
+ const usageComponent = ((_a = heading.usageLocation) === null || _a === void 0 ? void 0 : _a.filePath)
455
+ ? this.componentRegistry.get(heading.usageLocation.filePath)
456
+ : null;
457
+ const usageName = usageComponent
458
+ ? path.basename(usageComponent.filePath, path.extname(usageComponent.filePath))
459
+ : ((_b = heading.usageLocation) === null || _b === void 0 ? void 0 : _b.filePath)
460
+ ? path.basename(heading.usageLocation.filePath, path.extname(heading.usageLocation.filePath))
461
+ : null;
462
+ const messageSuffix = usageName ? ` (rendered via '${usageName}')` : '';
408
463
  results.push({
409
- filePath: ((_a = heading.usageLocation) === null || _a === void 0 ? void 0 : _a.filePath) || heading.heading.filePath,
410
- line: ((_b = heading.usageLocation) === null || _b === void 0 ? void 0 : _b.line) || heading.heading.line,
411
- column: ((_c = heading.usageLocation) === null || _c === void 0 ? void 0 : _c.column) || heading.heading.column,
412
- message: `Cross-component heading level skipped: <h${heading.heading.level}> after <h${lastLevel}>`,
464
+ filePath: locationFile,
465
+ line: locationLine,
466
+ column: locationColumn,
467
+ message: `Cross-component heading level skipped: <h${heading.heading.level}> after <h${lastLevel}>${messageSuffix}`,
413
468
  rule: 'enforceHeadingOrder'
414
469
  });
415
470
  }
416
471
  else if (heading.heading.level === 1 && lastLevel !== 1) {
472
+ const locationFile = heading.heading.filePath;
473
+ const locationLine = heading.heading.line;
474
+ const locationColumn = heading.heading.column;
475
+ const usageComponent = ((_c = heading.usageLocation) === null || _c === void 0 ? void 0 : _c.filePath)
476
+ ? this.componentRegistry.get(heading.usageLocation.filePath)
477
+ : null;
478
+ const usageName = usageComponent
479
+ ? path.basename(usageComponent.filePath, path.extname(usageComponent.filePath))
480
+ : ((_d = heading.usageLocation) === null || _d === void 0 ? void 0 : _d.filePath)
481
+ ? path.basename(heading.usageLocation.filePath, path.extname(heading.usageLocation.filePath))
482
+ : null;
483
+ const messageSuffix = usageName ? ` (rendered via '${usageName}')` : '';
417
484
  results.push({
418
- filePath: ((_d = heading.usageLocation) === null || _d === void 0 ? void 0 : _d.filePath) || heading.heading.filePath,
419
- line: ((_e = heading.usageLocation) === null || _e === void 0 ? void 0 : _e.line) || heading.heading.line,
420
- column: ((_f = heading.usageLocation) === null || _f === void 0 ? void 0 : _f.column) || heading.heading.column,
421
- message: `Cross-component heading level skipped: <h${heading.heading.level}> after <h${lastLevel}>`,
485
+ filePath: locationFile,
486
+ line: locationLine,
487
+ column: locationColumn,
488
+ message: `Cross-component heading level skipped: <h${heading.heading.level}> after <h${lastLevel}>${messageSuffix}`,
422
489
  rule: 'enforceHeadingOrder'
423
490
  });
424
491
  }
@@ -36,8 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.ComponentPathResolver = void 0;
37
37
  const fs = __importStar(require("fs/promises"));
38
38
  const path = __importStar(require("path"));
39
- // eslint-disable-next-line @typescript-eslint/no-var-requires
40
- const glob = require('glob');
39
+ const glob_1 = require("glob");
41
40
  let vscodeApi;
42
41
  try {
43
42
  vscodeApi = require('vscode');
@@ -182,8 +181,10 @@ class ComponentPathResolver {
182
181
  let alias = ComponentPathResolver.aliasCache.get(prefix);
183
182
  if (!alias) {
184
183
  const pattern = `**/${prefix}/**/*.{tsx,jsx,ts,js}`;
185
- const files = await new Promise((resolve, reject) => {
186
- glob(pattern, { cwd: ComponentPathResolver.rootDir, ignore: '**/node_modules/**', nodir: true }, (err, matches) => (err ? reject(err) : resolve(matches)));
184
+ const files = await (0, glob_1.glob)(pattern, {
185
+ cwd: ComponentPathResolver.rootDir,
186
+ ignore: '**/node_modules/**',
187
+ nodir: true,
187
188
  });
188
189
  alias = new Map();
189
190
  for (const relPath of files.slice(0, ComponentPathResolver.aliasFileLimit)) {
@@ -218,8 +219,10 @@ class ComponentPathResolver {
218
219
  }
219
220
  continue;
220
221
  }
221
- const matches = await new Promise((resolve, reject) => {
222
- glob(ptn, { cwd: ComponentPathResolver.rootDir, ignore: '**/node_modules/**', nodir: true }, (err, files) => (err ? reject(err) : resolve(files)));
222
+ const matches = await (0, glob_1.glob)(ptn, {
223
+ cwd: ComponentPathResolver.rootDir,
224
+ ignore: '**/node_modules/**',
225
+ nodir: true,
223
226
  });
224
227
  if (matches.length) {
225
228
  result = path.resolve(ComponentPathResolver.rootDir, matches[0]);
package/out/linter.d.ts CHANGED
@@ -18,6 +18,12 @@ export interface LintResult {
18
18
  rule: string;
19
19
  severity?: RuleSeverity;
20
20
  filePath?: string;
21
+ related?: Array<{
22
+ filePath: string;
23
+ line: number;
24
+ column: number;
25
+ message?: string;
26
+ }>;
21
27
  }
22
28
  export interface Rule {
23
29
  name: string;
package/out/src/cli.js CHANGED
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- const glob_1 = __importDefault(require("glob"));
7
+ const glob_1 = require("glob");
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const project_linter_1 = require("./project-linter");
10
10
  function parsePatterns(inputs) {
@@ -66,7 +66,7 @@ async function run() {
66
66
  }
67
67
  const files = new Set();
68
68
  for (const pattern of patterns) {
69
- const matches = glob_1.default.sync(pattern, { nodir: true });
69
+ const matches = (0, glob_1.globSync)(pattern, { nodir: true });
70
70
  for (const m of matches)
71
71
  files.add(m);
72
72
  }
@@ -318,61 +318,105 @@ class ComponentAnalyzer {
318
318
  return results;
319
319
  }
320
320
  findCrossComponentH1Issues(results) {
321
- var _a;
321
+ var _a, _b;
322
322
  const entryPoints = this.findEntryPoints();
323
+ const emitted = new Set();
324
+ const getDisplayName = (component) => {
325
+ if (component.name)
326
+ return component.name;
327
+ return path.basename(component.filePath, path.extname(component.filePath));
328
+ };
329
+ const addResult = (result) => {
330
+ const key = `${result.rule}|${result.filePath}|${result.line}|${result.column}|${result.message}`;
331
+ if (emitted.has(key))
332
+ return;
333
+ emitted.add(key);
334
+ results.push(result);
335
+ };
323
336
  for (const entry of entryPoints) {
324
337
  const comps = this.findComponentsWithRule(entry, 'singleH1', 0);
325
- if (comps.length > 1) {
326
- for (let i = 1; i < comps.length; i++) {
327
- const comp = comps[i];
328
- if (!comp || !comp.name) {
329
- console.error('[ZemDomu] Missing component or name during cross-component analysis', comp);
338
+ if (comps.length <= 1)
339
+ continue;
340
+ const conflictMap = new Map();
341
+ for (const comp of comps) {
342
+ const conflicts = comps
343
+ .filter(other => other.filePath !== comp.filePath)
344
+ .map(getDisplayName);
345
+ if (conflicts.length)
346
+ conflictMap.set(comp.filePath, conflicts);
347
+ }
348
+ const usageMap = new Map();
349
+ const usageStack = new Set();
350
+ const collectUsage = (component, depth = 0) => {
351
+ if (this.maxDepth !== undefined && depth > this.maxDepth)
352
+ return;
353
+ if (usageStack.has(component.filePath))
354
+ return;
355
+ usageStack.add(component.filePath);
356
+ for (const ref of component.usesComponents) {
357
+ if (!ref.path || !this.componentRegistry.has(ref.path))
330
358
  continue;
331
- }
332
- const ref = this.findReferenceForComp(entry, comp.filePath, 0);
333
- if (ref) {
334
- // Use first JSX usage location instead of import location
335
- const location = ref.usageLocations[0] || ref.sourceLocation;
336
- results.push({
337
- filePath: entry.filePath,
338
- line: location.line,
339
- column: location.column,
340
- message: `Multiple <h1> tags: component '${comp.name}' brings an extra <h1>. Use a lower-level heading.`,
341
- rule: 'singleH1'
359
+ const child = this.componentRegistry.get(ref.path);
360
+ if (!usageMap.has(child.filePath))
361
+ usageMap.set(child.filePath, []);
362
+ const locations = ref.usageLocations.length > 0 ? ref.usageLocations : [ref.sourceLocation];
363
+ for (const loc of locations) {
364
+ usageMap.get(child.filePath).push({
365
+ parent: component,
366
+ location: { filePath: component.filePath, line: loc.line, column: loc.column },
342
367
  });
343
368
  }
344
- else {
345
- const issue = (_a = comp.issues.get('singleH1')) === null || _a === void 0 ? void 0 : _a[0];
346
- if (issue) {
347
- results.push({
348
- filePath: comp.filePath,
349
- line: issue.line,
350
- column: issue.column,
351
- message: `Multiple <h1> across components - consider using lower-level headings.`,
352
- rule: 'singleH1'
353
- });
354
- }
355
- }
369
+ collectUsage(child, depth + 1);
370
+ }
371
+ usageStack.delete(component.filePath);
372
+ };
373
+ collectUsage(entry, 0);
374
+ for (const comp of comps) {
375
+ const conflicts = conflictMap.get(comp.filePath);
376
+ if (!conflicts || !conflicts.length)
377
+ continue;
378
+ const compName = getDisplayName(comp);
379
+ const issues = (_a = comp.issues.get('singleH1')) !== null && _a !== void 0 ? _a : [];
380
+ if (!issues.length)
381
+ continue;
382
+ const conflictText = conflicts.map(name => `'${name}'`).join(', ');
383
+ const usageEntries = (_b = usageMap.get(comp.filePath)) !== null && _b !== void 0 ? _b : [];
384
+ const usageRelated = usageEntries.map(u => ({
385
+ filePath: u.location.filePath,
386
+ line: u.location.line,
387
+ column: u.location.column,
388
+ message: `Rendered via '${getDisplayName(u.parent)}'`,
389
+ }));
390
+ for (const issue of issues) {
391
+ addResult({
392
+ filePath: comp.filePath,
393
+ line: issue.line,
394
+ column: issue.column,
395
+ message: `Multiple <h1> tags across components. This <h1> in '${compName}' conflicts with ${conflictText}.`,
396
+ rule: 'singleH1',
397
+ related: usageRelated,
398
+ });
399
+ }
400
+ const childIssueLocations = issues.map(issue => ({
401
+ filePath: comp.filePath,
402
+ line: issue.line,
403
+ column: issue.column,
404
+ message: `Defined in '${compName}'`,
405
+ }));
406
+ for (const usage of usageEntries) {
407
+ const parentName = getDisplayName(usage.parent);
408
+ addResult({
409
+ filePath: usage.location.filePath,
410
+ line: usage.location.line,
411
+ column: usage.location.column,
412
+ message: `Component '${compName}' renders an extra <h1> that conflicts with ${conflictText}.`,
413
+ rule: 'singleH1',
414
+ related: childIssueLocations.length ? childIssueLocations : undefined,
415
+ });
356
416
  }
357
417
  }
358
418
  }
359
419
  }
360
- findReferenceForComp(root, targetPath, depth = 0) {
361
- if (this.maxDepth !== undefined && depth > this.maxDepth)
362
- return null;
363
- for (const ref of root.usesComponents) {
364
- if (ref.path === targetPath)
365
- return ref;
366
- }
367
- for (const ref of root.usesComponents) {
368
- if (ref.path && this.componentRegistry.has(ref.path)) {
369
- const nested = this.findReferenceForComp(this.componentRegistry.get(ref.path), targetPath, depth + 1);
370
- if (nested)
371
- return ref;
372
- }
373
- }
374
- return null;
375
- }
376
420
  /**
377
421
  * Improved implementation to find heading order issues across components
378
422
  */
@@ -389,7 +433,7 @@ class ComponentAnalyzer {
389
433
  * and checks for heading level issues
390
434
  */
391
435
  analyzeHeadingHierarchy(component, results, depth = 0) {
392
- var _a, _b, _c, _d, _e, _f;
436
+ var _a, _b, _c, _d;
393
437
  if (this.maxDepth !== undefined && depth > this.maxDepth)
394
438
  return;
395
439
  if (this.processingComponentStack.has(component.filePath)) {
@@ -404,21 +448,44 @@ class ComponentAnalyzer {
404
448
  for (const heading of allHeadings) {
405
449
  if (lastLevel > 0) {
406
450
  if (heading.heading.level > lastLevel + 1) {
407
- // We found a heading level skip
451
+ const locationFile = heading.heading.filePath;
452
+ const locationLine = heading.heading.line;
453
+ const locationColumn = heading.heading.column;
454
+ const usageComponent = ((_a = heading.usageLocation) === null || _a === void 0 ? void 0 : _a.filePath)
455
+ ? this.componentRegistry.get(heading.usageLocation.filePath)
456
+ : null;
457
+ const usageName = usageComponent
458
+ ? path.basename(usageComponent.filePath, path.extname(usageComponent.filePath))
459
+ : ((_b = heading.usageLocation) === null || _b === void 0 ? void 0 : _b.filePath)
460
+ ? path.basename(heading.usageLocation.filePath, path.extname(heading.usageLocation.filePath))
461
+ : null;
462
+ const messageSuffix = usageName ? ` (rendered via '${usageName}')` : '';
408
463
  results.push({
409
- filePath: ((_a = heading.usageLocation) === null || _a === void 0 ? void 0 : _a.filePath) || heading.heading.filePath,
410
- line: ((_b = heading.usageLocation) === null || _b === void 0 ? void 0 : _b.line) || heading.heading.line,
411
- column: ((_c = heading.usageLocation) === null || _c === void 0 ? void 0 : _c.column) || heading.heading.column,
412
- message: `Cross-component heading level skipped: <h${heading.heading.level}> after <h${lastLevel}>`,
464
+ filePath: locationFile,
465
+ line: locationLine,
466
+ column: locationColumn,
467
+ message: `Cross-component heading level skipped: <h${heading.heading.level}> after <h${lastLevel}>${messageSuffix}`,
413
468
  rule: 'enforceHeadingOrder'
414
469
  });
415
470
  }
416
471
  else if (heading.heading.level === 1 && lastLevel !== 1) {
472
+ const locationFile = heading.heading.filePath;
473
+ const locationLine = heading.heading.line;
474
+ const locationColumn = heading.heading.column;
475
+ const usageComponent = ((_c = heading.usageLocation) === null || _c === void 0 ? void 0 : _c.filePath)
476
+ ? this.componentRegistry.get(heading.usageLocation.filePath)
477
+ : null;
478
+ const usageName = usageComponent
479
+ ? path.basename(usageComponent.filePath, path.extname(usageComponent.filePath))
480
+ : ((_d = heading.usageLocation) === null || _d === void 0 ? void 0 : _d.filePath)
481
+ ? path.basename(heading.usageLocation.filePath, path.extname(heading.usageLocation.filePath))
482
+ : null;
483
+ const messageSuffix = usageName ? ` (rendered via '${usageName}')` : '';
417
484
  results.push({
418
- filePath: ((_d = heading.usageLocation) === null || _d === void 0 ? void 0 : _d.filePath) || heading.heading.filePath,
419
- line: ((_e = heading.usageLocation) === null || _e === void 0 ? void 0 : _e.line) || heading.heading.line,
420
- column: ((_f = heading.usageLocation) === null || _f === void 0 ? void 0 : _f.column) || heading.heading.column,
421
- message: `Cross-component heading level skipped: <h${heading.heading.level}> after <h${lastLevel}>`,
485
+ filePath: locationFile,
486
+ line: locationLine,
487
+ column: locationColumn,
488
+ message: `Cross-component heading level skipped: <h${heading.heading.level}> after <h${lastLevel}>${messageSuffix}`,
422
489
  rule: 'enforceHeadingOrder'
423
490
  });
424
491
  }
@@ -36,8 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.ComponentPathResolver = void 0;
37
37
  const fs = __importStar(require("fs/promises"));
38
38
  const path = __importStar(require("path"));
39
- // eslint-disable-next-line @typescript-eslint/no-var-requires
40
- const glob = require('glob');
39
+ const glob_1 = require("glob");
41
40
  let vscodeApi;
42
41
  try {
43
42
  vscodeApi = require('vscode');
@@ -182,8 +181,10 @@ class ComponentPathResolver {
182
181
  let alias = ComponentPathResolver.aliasCache.get(prefix);
183
182
  if (!alias) {
184
183
  const pattern = `**/${prefix}/**/*.{tsx,jsx,ts,js}`;
185
- const files = await new Promise((resolve, reject) => {
186
- glob(pattern, { cwd: ComponentPathResolver.rootDir, ignore: '**/node_modules/**', nodir: true }, (err, matches) => (err ? reject(err) : resolve(matches)));
184
+ const files = await (0, glob_1.glob)(pattern, {
185
+ cwd: ComponentPathResolver.rootDir,
186
+ ignore: '**/node_modules/**',
187
+ nodir: true,
187
188
  });
188
189
  alias = new Map();
189
190
  for (const relPath of files.slice(0, ComponentPathResolver.aliasFileLimit)) {
@@ -218,8 +219,10 @@ class ComponentPathResolver {
218
219
  }
219
220
  continue;
220
221
  }
221
- const matches = await new Promise((resolve, reject) => {
222
- glob(ptn, { cwd: ComponentPathResolver.rootDir, ignore: '**/node_modules/**', nodir: true }, (err, files) => (err ? reject(err) : resolve(files)));
222
+ const matches = await (0, glob_1.glob)(ptn, {
223
+ cwd: ComponentPathResolver.rootDir,
224
+ ignore: '**/node_modules/**',
225
+ nodir: true,
223
226
  });
224
227
  if (matches.length) {
225
228
  result = path.resolve(ComponentPathResolver.rootDir, matches[0]);
@@ -1,41 +1,7 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
3
  exports.default = Button;
37
4
  const jsx_runtime_1 = require("react/jsx-runtime");
38
- const React = __importStar(require("react"));
39
5
  function Button() {
40
6
  return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h1", { children: "Button" }), (0, jsx_runtime_1.jsx)("h5", { children: "Subsection" })] }));
41
7
  }
@@ -1,44 +1,10 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
4
  };
38
5
  Object.defineProperty(exports, "__esModule", { value: true });
39
6
  exports.default = Page;
40
7
  const jsx_runtime_1 = require("react/jsx-runtime");
41
- const React = __importStar(require("react"));
42
8
  const Section_1 = __importDefault(require("@alias/Section"));
43
9
  function Page() {
44
10
  return ((0, jsx_runtime_1.jsxs)("main", { children: [(0, jsx_runtime_1.jsx)("h1", { children: "Hello" }), (0, jsx_runtime_1.jsx)(Section_1.default, {})] }));
@@ -1,44 +1,10 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
4
  };
38
5
  Object.defineProperty(exports, "__esModule", { value: true });
39
6
  exports.default = Section;
40
7
  const jsx_runtime_1 = require("react/jsx-runtime");
41
- const React = __importStar(require("react"));
42
8
  const SubSection_1 = __importDefault(require("@alias/SubSection"));
43
9
  function Section() {
44
10
  return ((0, jsx_runtime_1.jsxs)("section", { children: [(0, jsx_runtime_1.jsx)("h2", { children: "Section Title" }), (0, jsx_runtime_1.jsx)(SubSection_1.default, {})] }));
@@ -1,44 +1,10 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
4
  };
38
5
  Object.defineProperty(exports, "__esModule", { value: true });
39
6
  exports.default = SubSection;
40
7
  const jsx_runtime_1 = require("react/jsx-runtime");
41
- const React = __importStar(require("react"));
42
8
  const Button_1 = __importDefault(require("@alias/Button"));
43
9
  function SubSection() {
44
10
  return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { children: "SubSection" }), (0, jsx_runtime_1.jsx)(Button_1.default, {})] }));
@@ -41,9 +41,15 @@ describe("cross component heading order", () => {
41
41
  assert_1.default.ok(((_a = byRule["singleH1"]) !== null && _a !== void 0 ? _a : 0) >= 1, "Expected at least one singleH1");
42
42
  assert_1.default.ok(((_b = byRule["enforceHeadingOrder"]) !== null && _b !== void 0 ? _b : 0) >= 1, // set to >=2 if you expect more
43
43
  "Expected at least one enforceHeadingOrder");
44
- const usageLocations = results
45
- .filter((r) => r.rule === "enforceHeadingOrder" && "filePath" in r && r.filePath)
44
+ const singleH1Files = new Set(results
45
+ .filter((r) => r.rule === "singleH1" && r.filePath)
46
+ .map((r) => path_1.default.basename(r.filePath)));
47
+ assert_1.default.ok(singleH1Files.has("Page.tsx"), "Expected cross-component singleH1 to surface on Page.tsx");
48
+ assert_1.default.ok(singleH1Files.has("Button.tsx"), "Expected cross-component singleH1 to surface on Button.tsx");
49
+ assert_1.default.ok(singleH1Files.has("SubSection.tsx"), "Expected cross-component singleH1 to surface on SubSection.tsx usage");
50
+ const headingLocations = results
51
+ .filter((r) => r.rule === "enforceHeadingOrder" && r.filePath)
46
52
  .map((r) => path_1.default.basename(r.filePath));
47
- assert_1.default.ok(usageLocations.includes("SubSection.tsx"), "Expected heading order issue to surface on the component that renders the offending child");
53
+ assert_1.default.ok(headingLocations.includes("Button.tsx"), "Expected heading order issue to highlight the component containing the offending heading");
48
54
  });
49
55
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zemdomu",
3
- "version": "1.3.7",
3
+ "version": "1.3.9",
4
4
  "description": "Semantic HTML linter for HTML, JSX, and TSX. Detects accessibility, SEO, and structure issues before deployment.",
5
5
  "main": "./out/index.js",
6
6
  "types": "./out/index.d.ts",
@@ -42,25 +42,24 @@
42
42
  "author": "Zacharias Eryd Berlin",
43
43
  "license": "ISC",
44
44
  "devDependencies": {
45
- "@types/babel__traverse": "^7.20.7",
45
+ "@types/babel__traverse": "^7.28.0",
46
46
  "@types/babel-traverse": "^6.25.10",
47
47
  "@types/babel-types": "^7.0.16",
48
- "@types/glob": "^8.1.0",
49
48
  "@types/mocha": "^10.0.10",
50
- "@types/node": "^22.15.17",
51
- "@types/react": "^19.1.9",
52
- "@types/react-dom": "^19.1.7",
53
- "esbuild": "^0.25.5",
54
- "mocha": "^10.8.2",
55
- "typescript": "^5.8.2"
49
+ "@types/node": "^24.9.1",
50
+ "@types/react": "^19.2.2",
51
+ "@types/react-dom": "^19.2.2",
52
+ "esbuild": "^0.25.11",
53
+ "mocha": "^11.7.4",
54
+ "typescript": "^5.9.3"
56
55
  },
57
56
  "dependencies": {
58
- "@babel/parser": "^7.27.0",
59
- "@babel/traverse": "^7.27.0",
60
- "@babel/types": "^7.27.0",
61
- "glob": "^7.2.3",
62
- "react": "^19.1.1",
63
- "react-dom": "^19.1.1"
57
+ "@babel/parser": "^7.28.4",
58
+ "@babel/traverse": "^7.28.4",
59
+ "@babel/types": "^7.28.4",
60
+ "glob": "^11.0.3",
61
+ "react": "^19.2.0",
62
+ "react-dom": "^19.2.0"
64
63
  },
65
64
  "jest": {
66
65
  "transform": {