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 +2 -2
- package/out/component-analyzer.d.ts +0 -1
- package/out/component-analyzer.js +122 -55
- package/out/component-path-resolver.js +9 -6
- package/out/linter.d.ts +6 -0
- package/out/src/cli.js +2 -2
- package/out/src/component-analyzer.js +122 -55
- package/out/src/component-path-resolver.js +9 -6
- package/out/tests/crossComponent/Button.js +0 -34
- package/out/tests/crossComponent/Page.js +0 -34
- package/out/tests/crossComponent/Section.js +0 -34
- package/out/tests/crossComponent/SubSection.js +0 -34
- package/out/tests/crossComponent/cross-heading-order.test.js +9 -3
- package/package.json +14 -15
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 =
|
|
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.
|
|
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
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
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
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
410
|
-
line:
|
|
411
|
-
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:
|
|
419
|
-
line:
|
|
420
|
-
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
|
-
|
|
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
|
|
186
|
-
|
|
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
|
|
222
|
-
|
|
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 =
|
|
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.
|
|
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
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
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
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
410
|
-
line:
|
|
411
|
-
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:
|
|
419
|
-
line:
|
|
420
|
-
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
|
-
|
|
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
|
|
186
|
-
|
|
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
|
|
222
|
-
|
|
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
|
|
45
|
-
.filter((r) => r.rule === "
|
|
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(
|
|
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.
|
|
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.
|
|
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": "^
|
|
51
|
-
"@types/react": "^19.
|
|
52
|
-
"@types/react-dom": "^19.
|
|
53
|
-
"esbuild": "^0.25.
|
|
54
|
-
"mocha": "^
|
|
55
|
-
"typescript": "^5.
|
|
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.
|
|
59
|
-
"@babel/traverse": "^7.
|
|
60
|
-
"@babel/types": "^7.
|
|
61
|
-
"glob": "^
|
|
62
|
-
"react": "^19.
|
|
63
|
-
"react-dom": "^19.
|
|
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": {
|