qualm-a11y 1.0.0

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.
Files changed (67) hide show
  1. package/README.md +201 -0
  2. package/RESEARCH.md +139 -0
  3. package/dist/analyser.d.ts +3 -0
  4. package/dist/analyser.d.ts.map +1 -0
  5. package/dist/analyser.js +77 -0
  6. package/dist/analyser.js.map +1 -0
  7. package/dist/cli.d.ts +3 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +128 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/diff.d.ts +3 -0
  12. package/dist/diff.d.ts.map +1 -0
  13. package/dist/diff.js +60 -0
  14. package/dist/diff.js.map +1 -0
  15. package/dist/parser.d.ts +3 -0
  16. package/dist/parser.d.ts.map +1 -0
  17. package/dist/parser.js +22 -0
  18. package/dist/parser.js.map +1 -0
  19. package/dist/reporters/json.d.ts +4 -0
  20. package/dist/reporters/json.d.ts.map +1 -0
  21. package/dist/reporters/json.js +20 -0
  22. package/dist/reporters/json.js.map +1 -0
  23. package/dist/reporters/sarif.d.ts +3 -0
  24. package/dist/reporters/sarif.d.ts.map +1 -0
  25. package/dist/reporters/sarif.js +40 -0
  26. package/dist/reporters/sarif.js.map +1 -0
  27. package/dist/reporters/terminal.d.ts +5 -0
  28. package/dist/reporters/terminal.d.ts.map +1 -0
  29. package/dist/reporters/terminal.js +83 -0
  30. package/dist/reporters/terminal.js.map +1 -0
  31. package/dist/rules/aria-correctness.d.ts +3 -0
  32. package/dist/rules/aria-correctness.d.ts.map +1 -0
  33. package/dist/rules/aria-correctness.js +52 -0
  34. package/dist/rules/aria-correctness.js.map +1 -0
  35. package/dist/rules/complexity.d.ts +4 -0
  36. package/dist/rules/complexity.d.ts.map +1 -0
  37. package/dist/rules/complexity.js +86 -0
  38. package/dist/rules/complexity.js.map +1 -0
  39. package/dist/rules/document-structure.d.ts +3 -0
  40. package/dist/rules/document-structure.d.ts.map +1 -0
  41. package/dist/rules/document-structure.js +51 -0
  42. package/dist/rules/document-structure.js.map +1 -0
  43. package/dist/rules/form-semantics.d.ts +3 -0
  44. package/dist/rules/form-semantics.d.ts.map +1 -0
  45. package/dist/rules/form-semantics.js +105 -0
  46. package/dist/rules/form-semantics.js.map +1 -0
  47. package/dist/rules/heading-hierarchy.d.ts +3 -0
  48. package/dist/rules/heading-hierarchy.d.ts.map +1 -0
  49. package/dist/rules/heading-hierarchy.js +37 -0
  50. package/dist/rules/heading-hierarchy.js.map +1 -0
  51. package/dist/rules/index.d.ts +4 -0
  52. package/dist/rules/index.d.ts.map +1 -0
  53. package/dist/rules/index.js +20 -0
  54. package/dist/rules/index.js.map +1 -0
  55. package/dist/rules/interactive-semantics.d.ts +3 -0
  56. package/dist/rules/interactive-semantics.d.ts.map +1 -0
  57. package/dist/rules/interactive-semantics.js +65 -0
  58. package/dist/rules/interactive-semantics.js.map +1 -0
  59. package/dist/rules/landmark-structure.d.ts +3 -0
  60. package/dist/rules/landmark-structure.d.ts.map +1 -0
  61. package/dist/rules/landmark-structure.js +60 -0
  62. package/dist/rules/landmark-structure.js.map +1 -0
  63. package/dist/types.d.ts +73 -0
  64. package/dist/types.d.ts.map +1 -0
  65. package/dist/types.js +37 -0
  66. package/dist/types.js.map +1 -0
  67. package/package.json +58 -0
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.headingHierarchyRule = void 0;
4
+ const HEADING_TAGS = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']);
5
+ exports.headingHierarchyRule = {
6
+ id: 'heading-hierarchy',
7
+ category: 'heading_hierarchy',
8
+ severity: 'warning',
9
+ meta: {
10
+ description: 'Enforce sequential heading levels without skipping ranks'
11
+ },
12
+ create(context) {
13
+ const headings = [];
14
+ return {
15
+ JSXOpeningElement(node) {
16
+ const name = node.name.type === 'JSXIdentifier' ? node.name.name : null;
17
+ if (!name || !HEADING_TAGS.has(name))
18
+ return;
19
+ const level = parseInt(name[1], 10);
20
+ headings.push({ level, node });
21
+ if (headings.length < 2)
22
+ return;
23
+ const prev = headings[headings.length - 2];
24
+ const curr = headings[headings.length - 1];
25
+ if (curr.level > prev.level + 1) {
26
+ context.report({
27
+ message: `Heading level skipped: <h${prev.level}> followed by <h${curr.level}>. Screen readers rely on sequential heading structure for navigation.`,
28
+ fixSuggestion: `Change <h${curr.level}> to <h${prev.level + 1}> to maintain sequential hierarchy.`,
29
+ location: context.getLoc(node),
30
+ snippet: context.getSourceCode(node)
31
+ });
32
+ }
33
+ }
34
+ };
35
+ }
36
+ };
37
+ //# sourceMappingURL=heading-hierarchy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heading-hierarchy.js","sourceRoot":"","sources":["../../src/rules/heading-hierarchy.ts"],"names":[],"mappings":";;;AAGA,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAEtD,QAAA,oBAAoB,GAAS;IACxC,EAAE,EAAE,mBAAmB;IACvB,QAAQ,EAAE,mBAAmB;IAC7B,QAAQ,EAAE,SAAS;IACnB,IAAI,EAAE;QACJ,WAAW,EAAE,0DAA0D;KACxE;IACD,MAAM,CAAC,OAAO;QACZ,MAAM,QAAQ,GAA0D,EAAE,CAAC;QAE3E,OAAO;YACL,iBAAiB,CAAC,IAAgC;gBAChD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;gBACxE,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,OAAO;gBAE7C,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACpC,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBAE/B,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;oBAAE,OAAO;gBAEhC,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;gBAC3C,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;gBAE3C,IAAI,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;oBAChC,OAAO,CAAC,MAAM,CAAC;wBACb,OAAO,EAAE,4BAA4B,IAAI,CAAC,KAAK,mBAAmB,IAAI,CAAC,KAAK,wEAAwE;wBACpJ,aAAa,EAAE,YAAY,IAAI,CAAC,KAAK,UAAU,IAAI,CAAC,KAAK,GAAG,CAAC,qCAAqC;wBAClG,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC;wBAC9B,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC;qBACrC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { Rule } from '../types';
2
+ export declare const activeRules: Rule[];
3
+ export { calculateComplexityMetrics } from './complexity';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAQhC,eAAO,MAAM,WAAW,EAAE,IAAI,EAO7B,CAAC;AAEF,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calculateComplexityMetrics = exports.activeRules = void 0;
4
+ const document_structure_1 = require("./document-structure");
5
+ const heading_hierarchy_1 = require("./heading-hierarchy");
6
+ const landmark_structure_1 = require("./landmark-structure");
7
+ const interactive_semantics_1 = require("./interactive-semantics");
8
+ const aria_correctness_1 = require("./aria-correctness");
9
+ const form_semantics_1 = require("./form-semantics");
10
+ exports.activeRules = [
11
+ document_structure_1.documentStructureRule,
12
+ heading_hierarchy_1.headingHierarchyRule,
13
+ landmark_structure_1.landmarkStructureRule,
14
+ interactive_semantics_1.interactiveSemanticsRule,
15
+ aria_correctness_1.ariaCorrectnessRule,
16
+ form_semantics_1.formSemanticsRule
17
+ ];
18
+ var complexity_1 = require("./complexity");
19
+ Object.defineProperty(exports, "calculateComplexityMetrics", { enumerable: true, get: function () { return complexity_1.calculateComplexityMetrics; } });
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":";;;AACA,6DAA6D;AAC7D,2DAA2D;AAC3D,6DAA6D;AAC7D,mEAAmE;AACnE,yDAAyD;AACzD,qDAAqD;AAExC,QAAA,WAAW,GAAW;IACjC,0CAAqB;IACrB,wCAAoB;IACpB,0CAAqB;IACrB,gDAAwB;IACxB,sCAAmB;IACnB,kCAAiB;CAClB,CAAC;AAEF,2CAA0D;AAAjD,wHAAA,0BAA0B,OAAA"}
@@ -0,0 +1,3 @@
1
+ import { Rule } from '../types';
2
+ export declare const interactiveSemanticsRule: Rule;
3
+ //# sourceMappingURL=interactive-semantics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interactive-semantics.d.ts","sourceRoot":"","sources":["../../src/rules/interactive-semantics.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAEhC,eAAO,MAAM,wBAAwB,EAAE,IAoEtC,CAAC"}
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.interactiveSemanticsRule = void 0;
4
+ exports.interactiveSemanticsRule = {
5
+ id: 'interactive-semantics',
6
+ category: 'interactive_semantics',
7
+ severity: 'error',
8
+ meta: {
9
+ description: 'Ensure interactive and media elements have accessible names'
10
+ },
11
+ create(context) {
12
+ return {
13
+ JSXOpeningElement(node) {
14
+ const name = node.name.type === 'JSXIdentifier' ? node.name.name : null;
15
+ if (!name)
16
+ return;
17
+ if (name === 'img') {
18
+ const hasAlt = node.attributes.some(attr => {
19
+ if (attr.type !== 'JSXAttribute')
20
+ return false;
21
+ return attr.name.type === 'JSXIdentifier' && attr.name.name === 'alt';
22
+ });
23
+ if (!hasAlt) {
24
+ context.report({
25
+ message: 'Image element is missing an alt attribute. Screen readers cannot describe this image to users.',
26
+ fixSuggestion: 'Add alt="descriptive text" for informative images, or alt="" for decorative images.',
27
+ location: context.getLoc(node),
28
+ snippet: context.getSourceCode(node)
29
+ });
30
+ }
31
+ }
32
+ },
33
+ JSXElement(node) {
34
+ const opening = node.openingElement;
35
+ const name = opening.name.type === 'JSXIdentifier' ? opening.name.name : null;
36
+ if (name !== 'button')
37
+ return;
38
+ const hasAriaLabel = opening.attributes.some(attr => {
39
+ if (attr.type !== 'JSXAttribute')
40
+ return false;
41
+ const attrName = attr.name.type === 'JSXIdentifier' ? attr.name.name : null;
42
+ return attrName === 'aria-label' || attrName === 'aria-labelledby';
43
+ });
44
+ if (hasAriaLabel)
45
+ return;
46
+ const hasTextChild = node.children.some(child => child.type === 'JSXText' && child.value.trim().length > 0);
47
+ if (hasTextChild)
48
+ return;
49
+ const hasOnlyNonTextChildren = node.children.length > 0 &&
50
+ node.children.every(child => child.type === 'JSXElement' ||
51
+ child.type === 'JSXExpressionContainer' ||
52
+ (child.type === 'JSXText' && child.value.trim() === ''));
53
+ if (hasOnlyNonTextChildren) {
54
+ context.report({
55
+ message: 'Button appears to contain only icon components without visible text. Add aria-label to describe the button action.',
56
+ fixSuggestion: 'Add aria-label="Description of action" to the button element.',
57
+ location: context.getLoc(opening),
58
+ snippet: context.getSourceCode(opening)
59
+ });
60
+ }
61
+ }
62
+ };
63
+ }
64
+ };
65
+ //# sourceMappingURL=interactive-semantics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interactive-semantics.js","sourceRoot":"","sources":["../../src/rules/interactive-semantics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Rule } from '../types';
2
+ export declare const landmarkStructureRule: Rule;
3
+ //# sourceMappingURL=landmark-structure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"landmark-structure.d.ts","sourceRoot":"","sources":["../../src/rules/landmark-structure.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAmBhC,eAAO,MAAM,qBAAqB,EAAE,IAsCnC,CAAC"}
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.landmarkStructureRule = void 0;
4
+ const LANDMARK_PATTERNS = [
5
+ { pattern: /\bnav(igation|bar)?\b/i, suggestion: '<nav>' },
6
+ { pattern: /\b(header|masthead|banner)\b/i, suggestion: '<header>' },
7
+ { pattern: /\b(footer|foot)\b/i, suggestion: '<footer>' },
8
+ { pattern: /\b(main|primary-content|page-content)\b/i, suggestion: '<main>' },
9
+ { pattern: /\b(aside|sidebar|complementary)\b/i, suggestion: '<aside>' },
10
+ { pattern: /\b(article|post|entry|blog-post)\b/i, suggestion: '<article>' },
11
+ ];
12
+ function getAttrStringValue(attr) {
13
+ if (!attr.value)
14
+ return null;
15
+ if (attr.value.type === 'Literal' && typeof attr.value.value === 'string') {
16
+ return attr.value.value;
17
+ }
18
+ return null;
19
+ }
20
+ exports.landmarkStructureRule = {
21
+ id: 'landmark-structure',
22
+ category: 'landmark_structure',
23
+ severity: 'warning',
24
+ meta: {
25
+ description: 'Replace generic containers that serve landmark roles with semantic HTML5 elements'
26
+ },
27
+ create(context) {
28
+ return {
29
+ JSXOpeningElement(node) {
30
+ const name = node.name.type === 'JSXIdentifier' ? node.name.name : null;
31
+ if (name !== 'div' && name !== 'span')
32
+ return;
33
+ for (const attr of node.attributes) {
34
+ if (attr.type !== 'JSXAttribute')
35
+ continue;
36
+ const attrName = attr.name.type === 'JSXIdentifier' ? attr.name.name : null;
37
+ // Only match className — id values are too often scroll anchors or JS hooks
38
+ // (e.g. id="onboarding-checklist-header") and produce false positives.
39
+ if (attrName !== 'className')
40
+ continue;
41
+ const value = getAttrStringValue(attr);
42
+ if (!value)
43
+ continue;
44
+ for (const { pattern, suggestion } of LANDMARK_PATTERNS) {
45
+ if (pattern.test(value)) {
46
+ context.report({
47
+ message: `<${name} ${attrName}="${value}"> appears to serve a landmark role. Use the semantic element ${suggestion} instead.`,
48
+ fixSuggestion: `Replace <${name}> with ${suggestion} to provide explicit landmark navigation for assistive technology users.`,
49
+ location: context.getLoc(node),
50
+ snippet: context.getSourceCode(node)
51
+ });
52
+ return;
53
+ }
54
+ }
55
+ }
56
+ }
57
+ };
58
+ }
59
+ };
60
+ //# sourceMappingURL=landmark-structure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"landmark-structure.js","sourceRoot":"","sources":["../../src/rules/landmark-structure.ts"],"names":[],"mappings":";;;AAGA,MAAM,iBAAiB,GAA8C;IACnE,EAAE,OAAO,EAAE,wBAAwB,EAAE,UAAU,EAAE,OAAO,EAAE;IAC1D,EAAE,OAAO,EAAE,+BAA+B,EAAE,UAAU,EAAE,UAAU,EAAE;IACpE,EAAE,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,UAAU,EAAE;IACzD,EAAE,OAAO,EAAE,0CAA0C,EAAE,UAAU,EAAE,QAAQ,EAAE;IAC7E,EAAE,OAAO,EAAE,oCAAoC,EAAE,UAAU,EAAE,SAAS,EAAE;IACxE,EAAE,OAAO,EAAE,qCAAqC,EAAE,UAAU,EAAE,WAAW,EAAE;CAC5E,CAAC;AAEF,SAAS,kBAAkB,CAAC,IAA2B;IACrD,IAAI,CAAC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAC7B,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC1E,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;IAC1B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAEY,QAAA,qBAAqB,GAAS;IACzC,EAAE,EAAE,oBAAoB;IACxB,QAAQ,EAAE,oBAAoB;IAC9B,QAAQ,EAAE,SAAS;IACnB,IAAI,EAAE;QACJ,WAAW,EAAE,mFAAmF;KACjG;IACD,MAAM,CAAC,OAAO;QACZ,OAAO;YACL,iBAAiB,CAAC,IAAgC;gBAChD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;gBACxE,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,MAAM;oBAAE,OAAO;gBAE9C,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;oBACnC,IAAI,IAAI,CAAC,IAAI,KAAK,cAAc;wBAAE,SAAS;oBAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;oBAC5E,4EAA4E;oBAC5E,uEAAuE;oBACvE,IAAI,QAAQ,KAAK,WAAW;wBAAE,SAAS;oBAEvC,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;oBACvC,IAAI,CAAC,KAAK;wBAAE,SAAS;oBAErB,KAAK,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,iBAAiB,EAAE,CAAC;wBACxD,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;4BACxB,OAAO,CAAC,MAAM,CAAC;gCACb,OAAO,EAAE,IAAI,IAAI,IAAI,QAAQ,KAAK,KAAK,iEAAiE,UAAU,WAAW;gCAC7H,aAAa,EAAE,YAAY,IAAI,UAAU,UAAU,0EAA0E;gCAC7H,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC;gCAC9B,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC;6BACrC,CAAC,CAAC;4BACH,OAAO;wBACT,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -0,0 +1,73 @@
1
+ export type Severity = 'error' | 'warning' | 'info';
2
+ export type ViolationCategory = 'document_structure' | 'heading_hierarchy' | 'landmark_structure' | 'interactive_semantics' | 'aria_correctness' | 'form_semantics';
3
+ export interface Location {
4
+ line: number;
5
+ column: number;
6
+ lineEnd?: number;
7
+ columnEnd?: number;
8
+ }
9
+ export interface Violation {
10
+ ruleId: string;
11
+ category: ViolationCategory;
12
+ severity: Severity;
13
+ message: string;
14
+ fixSuggestion: string;
15
+ location: Location;
16
+ snippet?: string;
17
+ }
18
+ export interface ComplexityMetrics {
19
+ cyclomaticComplexity: number;
20
+ cognitiveComplexity: number;
21
+ maxJsxNestingDepth: number;
22
+ linesOfCode: number;
23
+ nodeCount: number;
24
+ propsCount: number;
25
+ propDrillingDepth: number;
26
+ }
27
+ export interface FileAnalysisResult {
28
+ filePath: string;
29
+ violations: Violation[];
30
+ metrics: ComplexityMetrics;
31
+ semanticScore: number;
32
+ }
33
+ export interface DiffResult {
34
+ filePath: string;
35
+ before: {
36
+ violationsCount: number;
37
+ semanticScore: number;
38
+ metrics: ComplexityMetrics;
39
+ } | null;
40
+ after: {
41
+ violationsCount: number;
42
+ semanticScore: number;
43
+ metrics: ComplexityMetrics;
44
+ };
45
+ deltaSemanticScore: number;
46
+ addedViolations: Violation[];
47
+ removedViolations: Violation[];
48
+ regressionDetected: boolean;
49
+ regressionCategories: ViolationCategory[];
50
+ }
51
+ export interface Rule {
52
+ id: string;
53
+ category: ViolationCategory;
54
+ severity: Severity;
55
+ meta: {
56
+ description: string;
57
+ docsUrl?: string;
58
+ };
59
+ create(context: RuleContext): Record<string, (node: any) => void>;
60
+ }
61
+ export interface RuleContext {
62
+ report(violation: Omit<Violation, 'ruleId' | 'category' | 'severity'>): void;
63
+ getSourceCode(node: any): string;
64
+ getLoc(node: any): Location;
65
+ }
66
+ export declare class QualmParserError extends Error {
67
+ readonly line: number;
68
+ readonly column: number;
69
+ readonly filePath: string;
70
+ constructor(message: string, line: number, column: number, filePath: string);
71
+ }
72
+ export declare const PAPER_BETA_COEFFICIENTS: Record<ViolationCategory, number>;
73
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PAPER_BETA_COEFFICIENTS = exports.QualmParserError = void 0;
4
+ class QualmParserError extends Error {
5
+ constructor(message, line, column, filePath) {
6
+ super(message);
7
+ this.line = line;
8
+ this.column = column;
9
+ this.filePath = filePath;
10
+ this.name = 'QualmParserError';
11
+ }
12
+ }
13
+ exports.QualmParserError = QualmParserError;
14
+ // β coefficients from Sharma (2026) Table 5 — DiD estimates by axe-core violation category.
15
+ // The paper reports three direct categories. The remaining three (heading_hierarchy,
16
+ // landmark_structure, form_semantics) are derived from the semantic_naming β split
17
+ // and the document_structure finding. Weights are normalised so total β sums to a
18
+ // meaningful composite. See RESEARCH.md for derivation.
19
+ //
20
+ // Direct from Table 5:
21
+ // document_structure: β = +0.007 (largest point estimate — dominant AI-gen failure mode)
22
+ // aria_correctness: β = +0.002 (aria_specific in paper taxonomy)
23
+ // interactive_semantics: β = −0.003 (semantic_naming in paper; negative = AI may IMPROVE naming)
24
+ //
25
+ // Derived (no individual paper estimate; set to plausible midpoints):
26
+ // heading_hierarchy: β = +0.003
27
+ // landmark_structure: β = +0.004 (structural, close to document_structure)
28
+ // form_semantics: β = +0.002 (similar to aria_correctness)
29
+ exports.PAPER_BETA_COEFFICIENTS = {
30
+ document_structure: 0.007,
31
+ landmark_structure: 0.004,
32
+ heading_hierarchy: 0.003,
33
+ interactive_semantics: 0.003,
34
+ aria_correctness: 0.002,
35
+ form_semantics: 0.002,
36
+ };
37
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;;AAwEA,MAAa,gBAAiB,SAAQ,KAAK;IACzC,YACE,OAAe,EACC,IAAY,EACZ,MAAc,EACd,QAAgB;QAEhC,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,SAAI,GAAJ,IAAI,CAAQ;QACZ,WAAM,GAAN,MAAM,CAAQ;QACd,aAAQ,GAAR,QAAQ,CAAQ;QAGhC,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAVD,4CAUC;AAED,4FAA4F;AAC5F,qFAAqF;AACrF,mFAAmF;AACnF,kFAAkF;AAClF,wDAAwD;AACxD,EAAE;AACF,uBAAuB;AACvB,8FAA8F;AAC9F,wEAAwE;AACxE,mGAAmG;AACnG,EAAE;AACF,sEAAsE;AACtE,qCAAqC;AACrC,gFAAgF;AAChF,oEAAoE;AACvD,QAAA,uBAAuB,GAAsC;IACxE,kBAAkB,EAAI,KAAK;IAC3B,kBAAkB,EAAI,KAAK;IAC3B,iBAAiB,EAAK,KAAK;IAC3B,qBAAqB,EAAE,KAAK;IAC5B,gBAAgB,EAAM,KAAK;IAC3B,cAAc,EAAQ,KAAK;CAC5B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "qualm-a11y",
3
+ "version": "1.0.0",
4
+ "description": "Static AST-level quality analyser for LLM-generated React/TypeScript code. Operationalises findings from Sharma (2026).",
5
+ "bin": {
6
+ "qualm-a11y": "./dist/cli.js"
7
+ },
8
+ "main": "./dist/analyser.js",
9
+ "types": "./dist/types.d.ts",
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "RESEARCH.md"
14
+ ],
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "scripts": {
19
+ "build": "tsc && chmod +x dist/cli.js",
20
+ "test": "jest",
21
+ "lint": "eslint src --ext .ts",
22
+ "prepublishOnly": "npm run build && npm test"
23
+ },
24
+ "dependencies": {
25
+ "@typescript-eslint/parser": "^7.0.0",
26
+ "@typescript-eslint/typescript-estree": "^7.0.0",
27
+ "@typescript-eslint/utils": "^7.0.0",
28
+ "chalk": "^5.0.0",
29
+ "commander": "^12.0.0",
30
+ "glob": "^10.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/jest": "^29.0.0",
34
+ "@types/node": "^20.0.0",
35
+ "jest": "^29.0.0",
36
+ "ts-jest": "^29.0.0",
37
+ "typescript": "^5.0.0"
38
+ },
39
+ "keywords": [
40
+ "accessibility",
41
+ "static-analysis",
42
+ "react",
43
+ "typescript",
44
+ "llm",
45
+ "code-quality",
46
+ "ast"
47
+ ],
48
+ "license": "MIT",
49
+ "author": "Somil Sharma <iamsomilsharma@gmail.com>",
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "https://github.com/SomilKSharma/qualm.git"
53
+ },
54
+ "homepage": "https://github.com/SomilKSharma/qualm#readme",
55
+ "bugs": {
56
+ "url": "https://github.com/SomilKSharma/qualm/issues"
57
+ }
58
+ }