zemdomu 1.1.0 → 1.1.3

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.
@@ -1,454 +1,461 @@
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
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.ComponentAnalyzer = void 0;
40
- const fs = __importStar(require("fs/promises"));
41
- const path = __importStar(require("path"));
42
- const parser_1 = require("@babel/parser");
43
- const traverse_1 = __importDefault(require("@babel/traverse"));
44
- const t = __importStar(require("@babel/types"));
45
- const component_path_resolver_1 = require("./component-path-resolver");
46
- class ComponentAnalyzer {
47
- constructor(options, perf) {
48
- this.componentRegistry = new Map();
49
- this.importToComponentMap = new Map();
50
- this.processingComponentStack = new Set(); // To prevent circular references
51
- this.resolver = new component_path_resolver_1.ComponentPathResolver();
52
- this.options = options;
53
- this.perf = perf;
54
- }
55
- async analyzeFile(filePath) {
56
- var _a;
57
- const start = Date.now();
58
- try {
59
- const content = await fs.readFile(filePath, 'utf8');
60
- if (!/\.(jsx|tsx)$/.test(filePath))
61
- return null;
62
- const { component, timings } = await this.extractComponentInfo(content, filePath);
63
- timings.total = Date.now() - start;
64
- (_a = this.perf) === null || _a === void 0 ? void 0 : _a.record(filePath, timings);
65
- return component;
66
- }
67
- catch (e) {
68
- console.error(`[ZemDomu] Error analyzing file ${filePath}:`, e);
69
- return null;
70
- }
71
- }
72
- async extractComponentInfo(content, filePath) {
73
- var _a, _b;
74
- const timings = {};
75
- let t0 = Date.now();
76
- const ast = (0, parser_1.parse)(content, { sourceType: 'module', plugins: ['typescript', 'jsx'] });
77
- timings.parse = Date.now() - t0;
78
- const componentName = path.basename(filePath, path.extname(filePath));
79
- const componentDef = {
80
- name: componentName,
81
- filePath,
82
- issues: new Map(),
83
- usesComponents: [],
84
- headings: []
85
- };
86
- // Track imported components
87
- const importedComponents = new Map();
88
- // Collect imports
89
- t0 = Date.now();
90
- (0, traverse_1.default)(ast, {
91
- ImportDeclaration(path) {
92
- const source = path.node.source.value;
93
- path.node.specifiers.forEach(spec => {
94
- if (t.isImportSpecifier(spec) || t.isImportDefaultSpecifier(spec)) {
95
- const name = spec.local.name;
96
- if (/^[A-Z]/.test(name)) {
97
- importedComponents.set(name, source);
98
- }
99
- }
100
- });
101
- }
102
- });
103
- timings.collectImports = Date.now() - t0;
104
- // Collect JSX usages and headings
105
- t0 = Date.now();
106
- (0, traverse_1.default)(ast, {
107
- JSXElement(path) {
108
- var _a, _b;
109
- const elt = path.node.openingElement.name;
110
- if (t.isJSXIdentifier(elt)) {
111
- const name = elt.name;
112
- const tag = name.toLowerCase();
113
- // Record headings
114
- if (/^h[1-6]$/.test(tag)) {
115
- const level = parseInt(tag.charAt(1), 10);
116
- const loc = (_a = elt.loc) === null || _a === void 0 ? void 0 : _a.start;
117
- if (loc) {
118
- componentDef.headings.push({
119
- level,
120
- line: loc.line - 1,
121
- column: loc.column,
122
- filePath
123
- });
124
- }
125
- }
126
- // Record component usage (only for capitalized components)
127
- if (/^[A-Z]/.test(name)) {
128
- const existingRef = componentDef.usesComponents.find(c => c.name === name);
129
- const loc = (_b = elt.loc) === null || _b === void 0 ? void 0 : _b.start;
130
- const location = loc ? { line: loc.line - 1, column: loc.column } : { line: 0, column: 0 };
131
- if (existingRef) {
132
- // Add usage location to existing reference
133
- existingRef.usageLocations.push(location);
134
- }
135
- else {
136
- // Create new component reference
137
- const rawImportPath = importedComponents.get(name) || null;
138
- componentDef.usesComponents.push({
139
- name,
140
- path: null, // Will be resolved later
141
- rawImportPath,
142
- sourceLocation: location,
143
- usageLocations: [location]
144
- });
145
- }
146
- }
147
- }
148
- }
149
- });
150
- timings.jsxCollect = Date.now() - t0;
151
- // Store import mappings for this file
152
- this.importToComponentMap.set(filePath, importedComponents);
153
- // Resolve import paths
154
- t0 = Date.now();
155
- for (const ref of componentDef.usesComponents) {
156
- if (ref.rawImportPath) {
157
- const t1 = Date.now();
158
- ref.path = await this.resolveComponentPath(ref.rawImportPath, filePath);
159
- timings[`resolve:${ref.rawImportPath}`] = Date.now() - t1;
160
- }
161
- }
162
- timings.resolvePaths = Date.now() - t0;
163
- // Check for heading order issues within this component
164
- t0 = Date.now();
165
- if ((_a = this.options.rules) === null || _a === void 0 ? void 0 : _a.enforceHeadingOrder) {
166
- let lastHeadingLevel = 0;
167
- const sortedHeadings = [...componentDef.headings].sort((a, b) => {
168
- if (a.line !== b.line)
169
- return a.line - b.line;
170
- return a.column - b.column;
171
- });
172
- for (const heading of sortedHeadings) {
173
- if (lastHeadingLevel && heading.level > lastHeadingLevel + 1) {
174
- componentDef.issues.set('enforceHeadingOrder', [
175
- ...(componentDef.issues.get('enforceHeadingOrder') || []),
176
- {
177
- line: heading.line,
178
- column: heading.column,
179
- message: `Heading level skipped: <h${heading.level}> after <h${lastHeadingLevel}>`,
180
- rule: 'enforceHeadingOrder'
181
- }
182
- ]);
183
- }
184
- lastHeadingLevel = heading.level;
185
- }
186
- }
187
- // Synthetic single-H1 issues
188
- if ((_b = this.options.rules) === null || _b === void 0 ? void 0 : _b.singleH1) {
189
- const h1Results = componentDef.headings
190
- .filter(h => h.level === 1)
191
- .map(h => ({ line: h.line, column: h.column, message: '<h1>', rule: 'singleH1' }));
192
- if (h1Results.length > 0) {
193
- componentDef.issues.set('singleH1', h1Results);
194
- }
195
- }
196
- timings.headingAnalysis = Date.now() - t0;
197
- // Register component
198
- this.componentRegistry.set(filePath, componentDef);
199
- return { component: componentDef, timings };
200
- }
201
- async resolveComponentPath(importPath, currentPath) {
202
- return this.resolver.resolve(importPath, currentPath);
203
- }
204
- registerComponent(component, issues) {
205
- for (const issue of issues) {
206
- const rule = issue.rule || this.getRuleType(issue.message);
207
- if (!component.issues.has(rule))
208
- component.issues.set(rule, []);
209
- component.issues.get(rule).push(issue);
210
- }
211
- this.componentRegistry.set(component.filePath, component);
212
- }
213
- getRuleType(msg) {
214
- if (msg.includes('<h1>'))
215
- return 'singleH1';
216
- if (msg.includes('Heading level'))
217
- return 'enforceHeadingOrder';
218
- if (msg.includes('<section>'))
219
- return 'requireSectionHeading';
220
- if (msg.includes('<img>'))
221
- return 'requireAltText';
222
- if (msg.includes('missing title attribute'))
223
- return 'requireIframeTitle';
224
- if (msg.includes('missing alt attribute') && msg.includes('input type="image"'))
225
- return 'requireImageInputAlt';
226
- if (msg.includes('<html>'))
227
- return 'requireHtmlLang';
228
- if (msg.includes('<button>'))
229
- return 'requireButtonText';
230
- if (msg.includes('Form control'))
231
- return 'requireLabelForFormControls';
232
- if (msg.includes('<li>'))
233
- return 'enforceListNesting';
234
- if (msg.includes('<a>'))
235
- return msg.includes('href') ? 'requireHrefOnAnchors' : 'requireLinkText';
236
- if (msg.includes('<table>'))
237
- return 'requireTableCaption';
238
- if (msg.includes('should not be empty'))
239
- return 'preventEmptyInlineTags';
240
- return 'other';
241
- }
242
- analyzeComponentTree() {
243
- var _a;
244
- const results = [];
245
- const cross = (_a = this.options.crossComponentAnalysis) !== null && _a !== void 0 ? _a : true;
246
- const rules = this.options.rules || {};
247
- if (!cross)
248
- return results;
249
- if (rules.singleH1)
250
- this.findCrossComponentH1Issues(results);
251
- if (rules.enforceHeadingOrder)
252
- this.findCrossComponentHeadingOrderIssues(results);
253
- return results;
254
- }
255
- findCrossComponentH1Issues(results) {
256
- const entryPoints = this.findEntryPoints();
257
- for (const entry of entryPoints) {
258
- const comps = this.findComponentsWithRule(entry, 'singleH1');
259
- if (comps.length > 1) {
260
- for (let i = 1; i < comps.length; i++) {
261
- const comp = comps[i];
262
- const ref = this.findReferenceForComp(entry, comp.filePath);
263
- if (ref) {
264
- // Use first JSX usage location instead of import location
265
- const location = ref.usageLocations[0] || ref.sourceLocation;
266
- results.push({
267
- filePath: entry.filePath,
268
- line: location.line,
269
- column: location.column,
270
- message: `Multiple <h1> tags: component '${comp.name}' brings an extra <h1>. Use a lower-level heading.`,
271
- rule: 'singleH1'
272
- });
273
- }
274
- else {
275
- const issue = comp.issues.get('singleH1')[0];
276
- results.push({
277
- filePath: comp.filePath,
278
- line: issue.line,
279
- column: issue.column,
280
- message: `Multiple <h1> across components - consider using lower-level headings.`,
281
- rule: 'singleH1'
282
- });
283
- }
284
- }
285
- }
286
- }
287
- }
288
- findReferenceForComp(root, targetPath) {
289
- for (const ref of root.usesComponents) {
290
- if (ref.path === targetPath)
291
- return ref;
292
- }
293
- for (const ref of root.usesComponents) {
294
- if (ref.path && this.componentRegistry.has(ref.path)) {
295
- const nested = this.findReferenceForComp(this.componentRegistry.get(ref.path), targetPath);
296
- if (nested)
297
- return ref;
298
- }
299
- }
300
- return null;
301
- }
302
- /**
303
- * Improved implementation to find heading order issues across components
304
- */
305
- findCrossComponentHeadingOrderIssues(results) {
306
- const entryPoints = this.findEntryPoints();
307
- for (const entry of entryPoints) {
308
- // Process each entry point as a document root
309
- this.processingComponentStack.clear();
310
- this.analyzeHeadingHierarchy(entry, results);
311
- }
312
- }
313
- /**
314
- * Collects all headings from a component and its children in document order
315
- * and checks for heading level issues
316
- */
317
- analyzeHeadingHierarchy(component, results) {
318
- var _a, _b, _c;
319
- if (this.processingComponentStack.has(component.filePath)) {
320
- // Avoid circular references
321
- return;
322
- }
323
- this.processingComponentStack.add(component.filePath);
324
- // Build a flattened view of all headings in document order
325
- const allHeadings = this.collectHeadingsInDocumentOrder(component);
326
- // Check for heading level issues
327
- let lastLevel = 0;
328
- for (const heading of allHeadings) {
329
- if (lastLevel > 0 && heading.heading.level > lastLevel + 1) {
330
- // We found a heading level skip
331
- results.push({
332
- filePath: ((_a = heading.usageLocation) === null || _a === void 0 ? void 0 : _a.filePath) || heading.heading.filePath,
333
- line: ((_b = heading.usageLocation) === null || _b === void 0 ? void 0 : _b.line) || heading.heading.line,
334
- column: ((_c = heading.usageLocation) === null || _c === void 0 ? void 0 : _c.column) || heading.heading.column,
335
- message: `Cross-component heading level skipped: <h${heading.heading.level}> after <h${lastLevel}>`,
336
- rule: 'enforceHeadingOrder'
337
- });
338
- }
339
- lastLevel = heading.heading.level;
340
- }
341
- this.processingComponentStack.delete(component.filePath);
342
- }
343
- /**
344
- * Collects all headings from a component and its children in document order
345
- */
346
- collectHeadingsInDocumentOrder(component) {
347
- // Sort headings within this component by line/column
348
- const localHeadings = [...component.headings].sort((a, b) => {
349
- if (a.line !== b.line)
350
- return a.line - b.line;
351
- return a.column - b.column;
352
- }).map(h => ({
353
- heading: h,
354
- usageLocation: null
355
- }));
356
- // Sort child components by their usage location
357
- const childComponents = component.usesComponents
358
- .filter(ref => ref.path && this.componentRegistry.has(ref.path))
359
- .sort((a, b) => {
360
- const aLoc = a.usageLocations[0] || a.sourceLocation;
361
- const bLoc = b.usageLocations[0] || b.sourceLocation;
362
- if (aLoc.line !== bLoc.line)
363
- return aLoc.line - bLoc.line;
364
- return aLoc.column - bLoc.column;
365
- });
366
- // Merge headings and child component headings in document order
367
- const allHeadings = [];
368
- let headingIndex = 0;
369
- let childIndex = 0;
370
- // This merges the local headings with child component headings
371
- // based on their position in the document
372
- while (headingIndex < localHeadings.length || childIndex < childComponents.length) {
373
- if (headingIndex >= localHeadings.length) {
374
- // No more local headings, process remaining children
375
- const childRef = childComponents[childIndex++];
376
- if (childRef.path && this.componentRegistry.has(childRef.path) && !this.processingComponentStack.has(childRef.path)) {
377
- const childComponent = this.componentRegistry.get(childRef.path);
378
- const usageLoc = childRef.usageLocations[0] || childRef.sourceLocation;
379
- const usageLocation = {
380
- filePath: component.filePath,
381
- line: usageLoc.line,
382
- column: usageLoc.column
383
- };
384
- this.processingComponentStack.add(childRef.path);
385
- const childHeadings = this.collectHeadingsInDocumentOrder(childComponent)
386
- .map(h => ({
387
- heading: h.heading,
388
- usageLocation: h.usageLocation || usageLocation
389
- }));
390
- this.processingComponentStack.delete(childRef.path);
391
- allHeadings.push(...childHeadings);
392
- }
393
- }
394
- else if (childIndex >= childComponents.length) {
395
- // No more children, add remaining local headings
396
- allHeadings.push(localHeadings[headingIndex++]);
397
- }
398
- else {
399
- // Compare positions to decide whether to add a local heading or process a child
400
- const nextHeading = localHeadings[headingIndex];
401
- const nextChild = childComponents[childIndex];
402
- const childLoc = nextChild.usageLocations[0] || nextChild.sourceLocation;
403
- if (nextHeading.heading.line < childLoc.line ||
404
- (nextHeading.heading.line === childLoc.line && nextHeading.heading.column < childLoc.column)) {
405
- // Local heading comes first
406
- allHeadings.push(nextHeading);
407
- headingIndex++;
408
- }
409
- else {
410
- // Child component comes first
411
- childIndex++;
412
- if (nextChild.path && this.componentRegistry.has(nextChild.path) && !this.processingComponentStack.has(nextChild.path)) {
413
- const childComponent = this.componentRegistry.get(nextChild.path);
414
- const usageLocation = {
415
- filePath: component.filePath,
416
- line: childLoc.line,
417
- column: childLoc.column
418
- };
419
- this.processingComponentStack.add(nextChild.path);
420
- const childHeadings = this.collectHeadingsInDocumentOrder(childComponent)
421
- .map(h => ({
422
- heading: h.heading,
423
- usageLocation: h.usageLocation || usageLocation
424
- }));
425
- this.processingComponentStack.delete(nextChild.path);
426
- allHeadings.push(...childHeadings);
427
- }
428
- }
429
- }
430
- }
431
- return allHeadings;
432
- }
433
- findEntryPoints() {
434
- const all = Array.from(this.componentRegistry.values());
435
- const imported = new Set();
436
- all.forEach(c => c.usesComponents.forEach(r => r.path && imported.add(r.path)));
437
- return all.filter(c => !imported.has(c.filePath));
438
- }
439
- findComponentsWithRule(root, rule) {
440
- const res = [];
441
- const visited = new Set();
442
- const dfs = (c) => {
443
- if (visited.has(c.filePath))
444
- return;
445
- visited.add(c.filePath);
446
- if (c.issues.has(rule))
447
- res.push(c);
448
- c.usesComponents.forEach(r => r.path && this.componentRegistry.has(r.path) && dfs(this.componentRegistry.get(r.path)));
449
- };
450
- dfs(root);
451
- return res;
452
- }
453
- }
454
- exports.ComponentAnalyzer = ComponentAnalyzer;
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.ComponentAnalyzer = void 0;
40
+ const fs = __importStar(require("fs/promises"));
41
+ const path = __importStar(require("path"));
42
+ const parser_1 = require("@babel/parser");
43
+ const traverse_1 = __importDefault(require("@babel/traverse"));
44
+ const t = __importStar(require("@babel/types"));
45
+ const component_path_resolver_1 = require("./component-path-resolver");
46
+ class ComponentAnalyzer {
47
+ constructor(options, perf) {
48
+ this.componentRegistry = new Map();
49
+ this.importToComponentMap = new Map();
50
+ this.processingComponentStack = new Set(); // To prevent circular references
51
+ this.resolver = new component_path_resolver_1.ComponentPathResolver();
52
+ this.options = options;
53
+ this.perf = perf;
54
+ }
55
+ async analyzeFile(filePath) {
56
+ var _a;
57
+ const start = Date.now();
58
+ try {
59
+ const content = await fs.readFile(filePath, 'utf8');
60
+ if (!/\.(jsx|tsx)$/.test(filePath))
61
+ return null;
62
+ const { component, timings } = await this.extractComponentInfo(content, filePath);
63
+ timings.total = Date.now() - start;
64
+ (_a = this.perf) === null || _a === void 0 ? void 0 : _a.record(filePath, timings);
65
+ return component;
66
+ }
67
+ catch (e) {
68
+ console.error(`[ZemDomu] Error analyzing file ${filePath}:`, e);
69
+ return null;
70
+ }
71
+ }
72
+ async extractComponentInfo(content, filePath) {
73
+ var _a, _b;
74
+ const timings = {};
75
+ let t0 = Date.now();
76
+ const ast = (0, parser_1.parse)(content, { sourceType: 'module', plugins: ['typescript', 'jsx'] });
77
+ timings.parse = Date.now() - t0;
78
+ const componentName = path.basename(filePath, path.extname(filePath));
79
+ const componentDef = {
80
+ name: componentName,
81
+ filePath,
82
+ issues: new Map(),
83
+ usesComponents: [],
84
+ headings: []
85
+ };
86
+ // Track imported components
87
+ const importedComponents = new Map();
88
+ // Collect imports
89
+ t0 = Date.now();
90
+ (0, traverse_1.default)(ast, {
91
+ ImportDeclaration(path) {
92
+ const source = path.node.source.value;
93
+ path.node.specifiers.forEach(spec => {
94
+ if (t.isImportSpecifier(spec) || t.isImportDefaultSpecifier(spec)) {
95
+ const name = spec.local.name;
96
+ if (/^[A-Z]/.test(name)) {
97
+ importedComponents.set(name, source);
98
+ }
99
+ }
100
+ });
101
+ }
102
+ });
103
+ timings.collectImports = Date.now() - t0;
104
+ // Collect JSX usages and headings
105
+ t0 = Date.now();
106
+ (0, traverse_1.default)(ast, {
107
+ JSXElement(path) {
108
+ var _a, _b;
109
+ const elt = path.node.openingElement.name;
110
+ if (t.isJSXIdentifier(elt)) {
111
+ const name = elt.name;
112
+ const tag = name.toLowerCase();
113
+ // Record headings
114
+ if (/^h[1-6]$/.test(tag)) {
115
+ const level = parseInt(tag.charAt(1), 10);
116
+ const loc = (_a = elt.loc) === null || _a === void 0 ? void 0 : _a.start;
117
+ if (loc) {
118
+ componentDef.headings.push({
119
+ level,
120
+ line: loc.line - 1,
121
+ column: loc.column,
122
+ filePath
123
+ });
124
+ }
125
+ }
126
+ // Record component usage (only for capitalized components)
127
+ if (/^[A-Z]/.test(name)) {
128
+ const existingRef = componentDef.usesComponents.find(c => c.name === name);
129
+ const loc = (_b = elt.loc) === null || _b === void 0 ? void 0 : _b.start;
130
+ const location = loc ? { line: loc.line - 1, column: loc.column } : { line: 0, column: 0 };
131
+ if (existingRef) {
132
+ // Add usage location to existing reference
133
+ existingRef.usageLocations.push(location);
134
+ }
135
+ else {
136
+ // Create new component reference
137
+ const rawImportPath = importedComponents.get(name) || null;
138
+ componentDef.usesComponents.push({
139
+ name,
140
+ path: null, // Will be resolved later
141
+ rawImportPath,
142
+ sourceLocation: location,
143
+ usageLocations: [location]
144
+ });
145
+ }
146
+ }
147
+ }
148
+ }
149
+ });
150
+ timings.jsxCollect = Date.now() - t0;
151
+ // Store import mappings for this file
152
+ this.importToComponentMap.set(filePath, importedComponents);
153
+ // Resolve import paths
154
+ t0 = Date.now();
155
+ for (const ref of componentDef.usesComponents) {
156
+ if (ref.rawImportPath) {
157
+ const t1 = Date.now();
158
+ ref.path = await this.resolveComponentPath(ref.rawImportPath, filePath);
159
+ timings[`resolve:${ref.rawImportPath}`] = Date.now() - t1;
160
+ }
161
+ }
162
+ timings.resolvePaths = Date.now() - t0;
163
+ // Check for heading order issues within this component
164
+ t0 = Date.now();
165
+ if ((_a = this.options.rules) === null || _a === void 0 ? void 0 : _a.enforceHeadingOrder) {
166
+ let lastHeadingLevel = 0;
167
+ const sortedHeadings = [...componentDef.headings].sort((a, b) => {
168
+ if (a.line !== b.line)
169
+ return a.line - b.line;
170
+ return a.column - b.column;
171
+ });
172
+ for (const heading of sortedHeadings) {
173
+ if (lastHeadingLevel && heading.level > lastHeadingLevel + 1) {
174
+ componentDef.issues.set('enforceHeadingOrder', [
175
+ ...(componentDef.issues.get('enforceHeadingOrder') || []),
176
+ {
177
+ line: heading.line,
178
+ column: heading.column,
179
+ message: `Heading level skipped: <h${heading.level}> after <h${lastHeadingLevel}>`,
180
+ rule: 'enforceHeadingOrder'
181
+ }
182
+ ]);
183
+ }
184
+ lastHeadingLevel = heading.level;
185
+ }
186
+ }
187
+ // Synthetic single-H1 issues
188
+ if ((_b = this.options.rules) === null || _b === void 0 ? void 0 : _b.singleH1) {
189
+ const h1Results = componentDef.headings
190
+ .filter(h => h.level === 1)
191
+ .map(h => ({ line: h.line, column: h.column, message: '<h1>', rule: 'singleH1' }));
192
+ if (h1Results.length > 0) {
193
+ componentDef.issues.set('singleH1', h1Results);
194
+ }
195
+ }
196
+ timings.headingAnalysis = Date.now() - t0;
197
+ // Register component
198
+ this.componentRegistry.set(filePath, componentDef);
199
+ return { component: componentDef, timings };
200
+ }
201
+ async resolveComponentPath(importPath, currentPath) {
202
+ return this.resolver.resolve(importPath, currentPath);
203
+ }
204
+ registerComponent(component, issues) {
205
+ for (const issue of issues) {
206
+ const rule = issue.rule || this.getRuleType(issue.message);
207
+ if (!component.issues.has(rule))
208
+ component.issues.set(rule, []);
209
+ component.issues.get(rule).push(issue);
210
+ }
211
+ this.componentRegistry.set(component.filePath, component);
212
+ }
213
+ getRuleType(msg) {
214
+ if (msg.includes('<h1>'))
215
+ return 'singleH1';
216
+ if (msg.includes('Heading level'))
217
+ return 'enforceHeadingOrder';
218
+ if (msg.includes('<section>'))
219
+ return 'requireSectionHeading';
220
+ if (msg.includes('<img>'))
221
+ return 'requireAltText';
222
+ if (msg.includes('missing title attribute'))
223
+ return 'requireIframeTitle';
224
+ if (msg.includes('missing alt attribute') && msg.includes('input type="image"'))
225
+ return 'requireImageInputAlt';
226
+ if (msg.includes('<html>'))
227
+ return 'requireHtmlLang';
228
+ if (msg.includes('<button>'))
229
+ return 'requireButtonText';
230
+ if (msg.includes('Form control'))
231
+ return 'requireLabelForFormControls';
232
+ if (msg.includes('<li>'))
233
+ return 'enforceListNesting';
234
+ if (msg.includes('<a>'))
235
+ return msg.includes('href') ? 'requireHrefOnAnchors' : 'requireLinkText';
236
+ if (msg.includes('<table>'))
237
+ return 'requireTableCaption';
238
+ if (msg.includes('should not be empty'))
239
+ return 'preventEmptyInlineTags';
240
+ return 'other';
241
+ }
242
+ analyzeComponentTree() {
243
+ var _a;
244
+ const results = [];
245
+ const cross = (_a = this.options.crossComponentAnalysis) !== null && _a !== void 0 ? _a : true;
246
+ const rules = this.options.rules || {};
247
+ if (!cross)
248
+ return results;
249
+ if (rules.singleH1)
250
+ this.findCrossComponentH1Issues(results);
251
+ if (rules.enforceHeadingOrder)
252
+ this.findCrossComponentHeadingOrderIssues(results);
253
+ return results;
254
+ }
255
+ findCrossComponentH1Issues(results) {
256
+ var _a;
257
+ const entryPoints = this.findEntryPoints();
258
+ for (const entry of entryPoints) {
259
+ const comps = this.findComponentsWithRule(entry, 'singleH1');
260
+ if (comps.length > 1) {
261
+ for (let i = 1; i < comps.length; i++) {
262
+ const comp = comps[i];
263
+ if (!comp || !comp.name) {
264
+ console.error('[ZemDomu] Missing component or name during cross-component analysis', comp);
265
+ continue;
266
+ }
267
+ const ref = this.findReferenceForComp(entry, comp.filePath);
268
+ if (ref) {
269
+ // Use first JSX usage location instead of import location
270
+ const location = ref.usageLocations[0] || ref.sourceLocation;
271
+ results.push({
272
+ filePath: entry.filePath,
273
+ line: location.line,
274
+ column: location.column,
275
+ message: `Multiple <h1> tags: component '${comp.name}' brings an extra <h1>. Use a lower-level heading.`,
276
+ rule: 'singleH1'
277
+ });
278
+ }
279
+ else {
280
+ const issue = (_a = comp.issues.get('singleH1')) === null || _a === void 0 ? void 0 : _a[0];
281
+ if (issue) {
282
+ results.push({
283
+ filePath: comp.filePath,
284
+ line: issue.line,
285
+ column: issue.column,
286
+ message: `Multiple <h1> across components - consider using lower-level headings.`,
287
+ rule: 'singleH1'
288
+ });
289
+ }
290
+ }
291
+ }
292
+ }
293
+ }
294
+ }
295
+ findReferenceForComp(root, targetPath) {
296
+ for (const ref of root.usesComponents) {
297
+ if (ref.path === targetPath)
298
+ return ref;
299
+ }
300
+ for (const ref of root.usesComponents) {
301
+ if (ref.path && this.componentRegistry.has(ref.path)) {
302
+ const nested = this.findReferenceForComp(this.componentRegistry.get(ref.path), targetPath);
303
+ if (nested)
304
+ return ref;
305
+ }
306
+ }
307
+ return null;
308
+ }
309
+ /**
310
+ * Improved implementation to find heading order issues across components
311
+ */
312
+ findCrossComponentHeadingOrderIssues(results) {
313
+ const entryPoints = this.findEntryPoints();
314
+ for (const entry of entryPoints) {
315
+ // Process each entry point as a document root
316
+ this.processingComponentStack.clear();
317
+ this.analyzeHeadingHierarchy(entry, results);
318
+ }
319
+ }
320
+ /**
321
+ * Collects all headings from a component and its children in document order
322
+ * and checks for heading level issues
323
+ */
324
+ analyzeHeadingHierarchy(component, results) {
325
+ var _a, _b, _c;
326
+ if (this.processingComponentStack.has(component.filePath)) {
327
+ // Avoid circular references
328
+ return;
329
+ }
330
+ this.processingComponentStack.add(component.filePath);
331
+ // Build a flattened view of all headings in document order
332
+ const allHeadings = this.collectHeadingsInDocumentOrder(component);
333
+ // Check for heading level issues
334
+ let lastLevel = 0;
335
+ for (const heading of allHeadings) {
336
+ if (lastLevel > 0 && heading.heading.level > lastLevel + 1) {
337
+ // We found a heading level skip
338
+ results.push({
339
+ filePath: ((_a = heading.usageLocation) === null || _a === void 0 ? void 0 : _a.filePath) || heading.heading.filePath,
340
+ line: ((_b = heading.usageLocation) === null || _b === void 0 ? void 0 : _b.line) || heading.heading.line,
341
+ column: ((_c = heading.usageLocation) === null || _c === void 0 ? void 0 : _c.column) || heading.heading.column,
342
+ message: `Cross-component heading level skipped: <h${heading.heading.level}> after <h${lastLevel}>`,
343
+ rule: 'enforceHeadingOrder'
344
+ });
345
+ }
346
+ lastLevel = heading.heading.level;
347
+ }
348
+ this.processingComponentStack.delete(component.filePath);
349
+ }
350
+ /**
351
+ * Collects all headings from a component and its children in document order
352
+ */
353
+ collectHeadingsInDocumentOrder(component) {
354
+ // Sort headings within this component by line/column
355
+ const localHeadings = [...component.headings].sort((a, b) => {
356
+ if (a.line !== b.line)
357
+ return a.line - b.line;
358
+ return a.column - b.column;
359
+ }).map(h => ({
360
+ heading: h,
361
+ usageLocation: null
362
+ }));
363
+ // Sort child components by their usage location
364
+ const childComponents = component.usesComponents
365
+ .filter(ref => ref.path && this.componentRegistry.has(ref.path))
366
+ .sort((a, b) => {
367
+ const aLoc = a.usageLocations[0] || a.sourceLocation;
368
+ const bLoc = b.usageLocations[0] || b.sourceLocation;
369
+ if (aLoc.line !== bLoc.line)
370
+ return aLoc.line - bLoc.line;
371
+ return aLoc.column - bLoc.column;
372
+ });
373
+ // Merge headings and child component headings in document order
374
+ const allHeadings = [];
375
+ let headingIndex = 0;
376
+ let childIndex = 0;
377
+ // This merges the local headings with child component headings
378
+ // based on their position in the document
379
+ while (headingIndex < localHeadings.length || childIndex < childComponents.length) {
380
+ if (headingIndex >= localHeadings.length) {
381
+ // No more local headings, process remaining children
382
+ const childRef = childComponents[childIndex++];
383
+ if (childRef.path && this.componentRegistry.has(childRef.path) && !this.processingComponentStack.has(childRef.path)) {
384
+ const childComponent = this.componentRegistry.get(childRef.path);
385
+ const usageLoc = childRef.usageLocations[0] || childRef.sourceLocation;
386
+ const usageLocation = {
387
+ filePath: component.filePath,
388
+ line: usageLoc.line,
389
+ column: usageLoc.column
390
+ };
391
+ this.processingComponentStack.add(childRef.path);
392
+ const childHeadings = this.collectHeadingsInDocumentOrder(childComponent)
393
+ .map(h => ({
394
+ heading: h.heading,
395
+ usageLocation: h.usageLocation || usageLocation
396
+ }));
397
+ this.processingComponentStack.delete(childRef.path);
398
+ allHeadings.push(...childHeadings);
399
+ }
400
+ }
401
+ else if (childIndex >= childComponents.length) {
402
+ // No more children, add remaining local headings
403
+ allHeadings.push(localHeadings[headingIndex++]);
404
+ }
405
+ else {
406
+ // Compare positions to decide whether to add a local heading or process a child
407
+ const nextHeading = localHeadings[headingIndex];
408
+ const nextChild = childComponents[childIndex];
409
+ const childLoc = nextChild.usageLocations[0] || nextChild.sourceLocation;
410
+ if (nextHeading.heading.line < childLoc.line ||
411
+ (nextHeading.heading.line === childLoc.line && nextHeading.heading.column < childLoc.column)) {
412
+ // Local heading comes first
413
+ allHeadings.push(nextHeading);
414
+ headingIndex++;
415
+ }
416
+ else {
417
+ // Child component comes first
418
+ childIndex++;
419
+ if (nextChild.path && this.componentRegistry.has(nextChild.path) && !this.processingComponentStack.has(nextChild.path)) {
420
+ const childComponent = this.componentRegistry.get(nextChild.path);
421
+ const usageLocation = {
422
+ filePath: component.filePath,
423
+ line: childLoc.line,
424
+ column: childLoc.column
425
+ };
426
+ this.processingComponentStack.add(nextChild.path);
427
+ const childHeadings = this.collectHeadingsInDocumentOrder(childComponent)
428
+ .map(h => ({
429
+ heading: h.heading,
430
+ usageLocation: h.usageLocation || usageLocation
431
+ }));
432
+ this.processingComponentStack.delete(nextChild.path);
433
+ allHeadings.push(...childHeadings);
434
+ }
435
+ }
436
+ }
437
+ }
438
+ return allHeadings;
439
+ }
440
+ findEntryPoints() {
441
+ const all = Array.from(this.componentRegistry.values());
442
+ const imported = new Set();
443
+ all.forEach(c => c.usesComponents.forEach(r => r.path && imported.add(r.path)));
444
+ return all.filter(c => !imported.has(c.filePath));
445
+ }
446
+ findComponentsWithRule(root, rule) {
447
+ const res = [];
448
+ const visited = new Set();
449
+ const dfs = (c) => {
450
+ if (visited.has(c.filePath))
451
+ return;
452
+ visited.add(c.filePath);
453
+ if (c.issues.has(rule))
454
+ res.push(c);
455
+ c.usesComponents.forEach(r => r.path && this.componentRegistry.has(r.path) && dfs(this.componentRegistry.get(r.path)));
456
+ };
457
+ dfs(root);
458
+ return res;
459
+ }
460
+ }
461
+ exports.ComponentAnalyzer = ComponentAnalyzer;