a11ylens 0.1.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.
- package/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/README.md +135 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +162 -0
- package/dist/config/defaults.d.ts +2 -0
- package/dist/config/defaults.js +9 -0
- package/dist/config/loader.d.ts +7 -0
- package/dist/config/loader.js +104 -0
- package/dist/engine/runA11yLens.d.ts +9 -0
- package/dist/engine/runA11yLens.js +158 -0
- package/dist/engine/styleResolver.d.ts +2 -0
- package/dist/engine/styleResolver.js +63 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +8 -0
- package/dist/parser/ast.d.ts +25 -0
- package/dist/parser/ast.js +169 -0
- package/dist/parser/css.d.ts +7 -0
- package/dist/parser/css.js +48 -0
- package/dist/reporters/consoleReporter.d.ts +2 -0
- package/dist/reporters/consoleReporter.js +67 -0
- package/dist/reporters/jsonReporter.d.ts +2 -0
- package/dist/reporters/jsonReporter.js +7 -0
- package/dist/reporters/sarifReporter.d.ts +2 -0
- package/dist/reporters/sarifReporter.js +95 -0
- package/dist/rules/clickableNonsemantic.d.ts +2 -0
- package/dist/rules/clickableNonsemantic.js +43 -0
- package/dist/rules/contrastRatio.d.ts +2 -0
- package/dist/rules/contrastRatio.js +149 -0
- package/dist/rules/engine.d.ts +4 -0
- package/dist/rules/engine.js +22 -0
- package/dist/rules/iconOnlyControl.d.ts +2 -0
- package/dist/rules/iconOnlyControl.js +50 -0
- package/dist/rules/imgAlt.d.ts +2 -0
- package/dist/rules/imgAlt.js +48 -0
- package/dist/rules/registry.d.ts +3 -0
- package/dist/rules/registry.js +19 -0
- package/dist/rules/routerLinkText.d.ts +2 -0
- package/dist/rules/routerLinkText.js +32 -0
- package/dist/rules/utils.d.ts +6 -0
- package/dist/rules/utils.js +51 -0
- package/dist/scanner/fileAnalyzer.d.ts +1 -0
- package/dist/scanner/fileAnalyzer.js +46 -0
- package/dist/scanner/fileSystemWalker.js +122 -0
- package/dist/scanner/fileWalker.d.ts +8 -0
- package/dist/scanner/fileWalker.js +126 -0
- package/dist/scanner/styleExtractor.d.ts +13 -0
- package/dist/scanner/styleExtractor.js +95 -0
- package/dist/scanner/templateExtractor.d.ts +13 -0
- package/dist/scanner/templateExtractor.js +101 -0
- package/dist/types/config.d.ts +9 -0
- package/dist/types/config.js +2 -0
- package/dist/types/results.d.ts +22 -0
- package/dist/types/results.js +2 -0
- package/dist/types/rules.d.ts +14 -0
- package/dist/types/rules.js +2 -0
- package/dist/types/style.d.ts +9 -0
- package/dist/types/style.js +2 -0
- package/package.json +65 -0
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 MADHAN MONISH JAYAKUMAR
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# A11yLens
|
|
2
|
+
|
|
3
|
+
A11yLens is a TypeScript Node.js CLI + library that scans Angular component templates (external `.component.html` and inline templates in `.component.ts`) for common accessibility issues. It reports findings in console, JSON, or SARIF formats and computes a project score.
|
|
4
|
+
|
|
5
|
+
No runtime dependencies are required.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g a11ylens
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or run via npx:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx a11ylens ./src
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## CLI
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
a11ylens [roots...] [options]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Options:
|
|
26
|
+
|
|
27
|
+
- `--config <path>`: Use a specific config file
|
|
28
|
+
- `--format <console|json|sarif>`: Output format (default: console)
|
|
29
|
+
- `--out <path>`: Write JSON/SARIF output to a file
|
|
30
|
+
- `--min-score <n>`: Exit non-zero when score is below `n`
|
|
31
|
+
- `--verbose`: Print extra diagnostics
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
a11ylens ./src
|
|
37
|
+
npx a11ylens ./apps/admin --format json
|
|
38
|
+
npx a11ylens ./src --format sarif --out a11ylens.sarif --min-score 85
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Library API
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { runA11yLens } from "a11ylens";
|
|
45
|
+
|
|
46
|
+
const result = runA11yLens(["./src"], {
|
|
47
|
+
cwd: process.cwd(),
|
|
48
|
+
overrides: { minScore: 90 }
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
console.log(result.score);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Configuration
|
|
55
|
+
|
|
56
|
+
A11yLens discovers config files by searching upward from the current working directory.
|
|
57
|
+
|
|
58
|
+
Supported files:
|
|
59
|
+
|
|
60
|
+
- `.a11ylensrc.json`
|
|
61
|
+
- `a11ylens.config.json`
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"includePatterns": ["*.component.html", "*.component.ts"],
|
|
68
|
+
"ignoreDirs": ["node_modules", ".git", "dist"],
|
|
69
|
+
"ignorePatterns": ["**/legacy/**"],
|
|
70
|
+
"styleFiles": ["./styles.css"],
|
|
71
|
+
"minScore": 85
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Notes:
|
|
76
|
+
|
|
77
|
+
- `includePatterns` and `ignorePatterns` use a minimal glob-like matcher (`*` prefix/suffix).
|
|
78
|
+
- `ignoreDirs` matches directory names anywhere in the path.
|
|
79
|
+
- `styleFiles` supports simple selectors (`.class`, `tag`, `tag.class`); complex selectors and CSS variables are ignored.
|
|
80
|
+
|
|
81
|
+
## Output Formats
|
|
82
|
+
|
|
83
|
+
- Console: human-friendly output with summary and score.
|
|
84
|
+
- JSON: machine-readable full results.
|
|
85
|
+
- SARIF: static analysis format for GitHub code scanning.
|
|
86
|
+
|
|
87
|
+
## Rules (Built-In)
|
|
88
|
+
|
|
89
|
+
- Note: rules are minimal and focus on common Angular template issues.
|
|
90
|
+
- `img-alt`: images require `alt`.
|
|
91
|
+
- `clickable-nonsemantic`: clickable div/span needs semantics.
|
|
92
|
+
- `routerlink-text`: router links need an accessible name.
|
|
93
|
+
- `icon-only-control`: icon-only controls need `aria-label`.
|
|
94
|
+
- `color-contrast`: text color must meet WCAG AA ratios when `color` and `background-color` are available (inline or simple class selectors).
|
|
95
|
+
|
|
96
|
+
## Exit Codes
|
|
97
|
+
|
|
98
|
+
- `0`: Success
|
|
99
|
+
- `1`: Runtime error
|
|
100
|
+
- `2`: `--min-score` provided and score is below the threshold
|
|
101
|
+
|
|
102
|
+
## CI Example (SARIF)
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
name: A11yLens SARIF
|
|
106
|
+
|
|
107
|
+
on:
|
|
108
|
+
workflow_dispatch:
|
|
109
|
+
|
|
110
|
+
jobs:
|
|
111
|
+
scan:
|
|
112
|
+
runs-on: ubuntu-latest
|
|
113
|
+
steps:
|
|
114
|
+
- uses: actions/checkout@v4
|
|
115
|
+
- uses: actions/setup-node@v4
|
|
116
|
+
with:
|
|
117
|
+
node-version: 20
|
|
118
|
+
cache: npm
|
|
119
|
+
- run: npm ci
|
|
120
|
+
- run: npm run build
|
|
121
|
+
- run: node dist/cli/index.js ./src --format sarif --out a11ylens.sarif
|
|
122
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
123
|
+
with:
|
|
124
|
+
sarif_file: a11ylens.sarif
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Development
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npm run build
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const runA11yLens_1 = require("../engine/runA11yLens");
|
|
39
|
+
const consoleReporter_1 = require("../reporters/consoleReporter");
|
|
40
|
+
const jsonReporter_1 = require("../reporters/jsonReporter");
|
|
41
|
+
const sarifReporter_1 = require("../reporters/sarifReporter");
|
|
42
|
+
const printUsage = () => {
|
|
43
|
+
console.log("Usage: a11ylens [roots...] [options]");
|
|
44
|
+
console.log("");
|
|
45
|
+
console.log("Options:");
|
|
46
|
+
console.log(" --config <path> Path to config file");
|
|
47
|
+
console.log(" --format <format> console | json | sarif");
|
|
48
|
+
console.log(" --out <path> Output file for json/sarif");
|
|
49
|
+
console.log(" --min-score <n> Minimum passing score");
|
|
50
|
+
console.log(" --verbose Extra logging");
|
|
51
|
+
console.log(" --help Show usage");
|
|
52
|
+
};
|
|
53
|
+
const parseArgs = (argv) => {
|
|
54
|
+
const roots = [];
|
|
55
|
+
let configPath;
|
|
56
|
+
let format = "console";
|
|
57
|
+
let outPath;
|
|
58
|
+
let minScore;
|
|
59
|
+
let verbose = false;
|
|
60
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
61
|
+
const arg = argv[i];
|
|
62
|
+
if (arg === "--help" || arg === "-h") {
|
|
63
|
+
printUsage();
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
if (arg.startsWith("--config")) {
|
|
67
|
+
const value = readValue(arg, argv[i + 1]);
|
|
68
|
+
if (value) {
|
|
69
|
+
configPath = value;
|
|
70
|
+
if (!arg.includes("="))
|
|
71
|
+
i += 1;
|
|
72
|
+
}
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (arg.startsWith("--format")) {
|
|
76
|
+
const value = readValue(arg, argv[i + 1]);
|
|
77
|
+
if (value === "console" || value === "json" || value === "sarif") {
|
|
78
|
+
format = value;
|
|
79
|
+
}
|
|
80
|
+
if (!arg.includes("="))
|
|
81
|
+
i += 1;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (arg.startsWith("--out")) {
|
|
85
|
+
const value = readValue(arg, argv[i + 1]);
|
|
86
|
+
if (value) {
|
|
87
|
+
outPath = value;
|
|
88
|
+
if (!arg.includes("="))
|
|
89
|
+
i += 1;
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (arg.startsWith("--min-score")) {
|
|
94
|
+
const value = readValue(arg, argv[i + 1]);
|
|
95
|
+
if (value) {
|
|
96
|
+
const parsed = Number(value);
|
|
97
|
+
if (!Number.isNaN(parsed)) {
|
|
98
|
+
minScore = parsed;
|
|
99
|
+
}
|
|
100
|
+
if (!arg.includes("="))
|
|
101
|
+
i += 1;
|
|
102
|
+
}
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (arg === "--verbose") {
|
|
106
|
+
verbose = true;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (arg.startsWith("-")) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
roots.push(arg);
|
|
113
|
+
}
|
|
114
|
+
if (roots.length === 0) {
|
|
115
|
+
roots.push("src");
|
|
116
|
+
}
|
|
117
|
+
return { roots, configPath, format, outPath, minScore, verbose };
|
|
118
|
+
};
|
|
119
|
+
const readValue = (arg, nextArg) => {
|
|
120
|
+
if (arg.includes("=")) {
|
|
121
|
+
return arg.split("=").slice(1).join("=");
|
|
122
|
+
}
|
|
123
|
+
return nextArg;
|
|
124
|
+
};
|
|
125
|
+
const main = () => {
|
|
126
|
+
const { roots, configPath, format, outPath, minScore, verbose } = parseArgs(process.argv.slice(2));
|
|
127
|
+
const cwd = process.cwd();
|
|
128
|
+
const overrides = minScore !== undefined ? { minScore } : undefined;
|
|
129
|
+
const config = (0, runA11yLens_1.resolveConfig)(cwd, configPath, overrides);
|
|
130
|
+
if (verbose) {
|
|
131
|
+
console.log("Config:", config);
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const result = (0, runA11yLens_1.runA11yLens)(roots, { cwd, configFilePath: configPath, overrides });
|
|
135
|
+
if (format === "console") {
|
|
136
|
+
(0, consoleReporter_1.reportConsole)(result, cwd);
|
|
137
|
+
}
|
|
138
|
+
else if (format === "json") {
|
|
139
|
+
const output = (0, jsonReporter_1.reportJson)(result);
|
|
140
|
+
writeOutput(output, outPath);
|
|
141
|
+
}
|
|
142
|
+
else if (format === "sarif") {
|
|
143
|
+
const output = (0, sarifReporter_1.reportSarif)(result);
|
|
144
|
+
writeOutput(output, outPath);
|
|
145
|
+
}
|
|
146
|
+
if (config.minScore !== undefined && result.score < config.minScore) {
|
|
147
|
+
process.exit(2);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
console.error("Error running A11yLens:", error instanceof Error ? error.message : error);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const writeOutput = (output, outPath) => {
|
|
156
|
+
if (outPath) {
|
|
157
|
+
fs.writeFileSync(outPath, output, "utf8");
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
console.log(output);
|
|
161
|
+
};
|
|
162
|
+
main();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defaultConfig = void 0;
|
|
4
|
+
exports.defaultConfig = {
|
|
5
|
+
includePatterns: ["*.component.html", "*.component.ts"],
|
|
6
|
+
ignoreDirs: ["node_modules", ".git", "dist"],
|
|
7
|
+
ignorePatterns: [],
|
|
8
|
+
styleFiles: []
|
|
9
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { A11yLensConfig, A11yLensConfigOverrides } from "../types/config";
|
|
2
|
+
export interface LoadedConfig {
|
|
3
|
+
config: A11yLensConfig;
|
|
4
|
+
configPath?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const loadConfig: (cwd: string, configPath?: string, overrides?: A11yLensConfigOverrides) => LoadedConfig;
|
|
7
|
+
export declare const mergeConfig: (base: A11yLensConfig, fileConfig: A11yLensConfigOverrides, overrides: A11yLensConfigOverrides) => A11yLensConfig;
|
|
@@ -0,0 +1,104 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.mergeConfig = exports.loadConfig = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const defaults_1 = require("./defaults");
|
|
40
|
+
const configFileNames = [".a11ylensrc.json", "a11ylens.config.json"];
|
|
41
|
+
const loadConfig = (cwd, configPath, overrides) => {
|
|
42
|
+
const resolvedCwd = path.resolve(cwd);
|
|
43
|
+
const discoveredPath = configPath ? path.resolve(configPath) : findConfigUpwards(resolvedCwd);
|
|
44
|
+
let fileConfig = {};
|
|
45
|
+
if (discoveredPath) {
|
|
46
|
+
const content = readConfigFile(discoveredPath);
|
|
47
|
+
if (content)
|
|
48
|
+
fileConfig = content;
|
|
49
|
+
}
|
|
50
|
+
const merged = (0, exports.mergeConfig)(defaults_1.defaultConfig, fileConfig, overrides !== null && overrides !== void 0 ? overrides : {});
|
|
51
|
+
return { config: merged, configPath: discoveredPath };
|
|
52
|
+
};
|
|
53
|
+
exports.loadConfig = loadConfig;
|
|
54
|
+
const mergeConfig = (base, fileConfig, overrides) => {
|
|
55
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
56
|
+
return {
|
|
57
|
+
includePatterns: normalizeStringArray((_b = (_a = overrides.includePatterns) !== null && _a !== void 0 ? _a : fileConfig.includePatterns) !== null && _b !== void 0 ? _b : base.includePatterns),
|
|
58
|
+
ignoreDirs: normalizeStringArray((_d = (_c = overrides.ignoreDirs) !== null && _c !== void 0 ? _c : fileConfig.ignoreDirs) !== null && _d !== void 0 ? _d : base.ignoreDirs),
|
|
59
|
+
ignorePatterns: normalizeStringArray((_f = (_e = overrides.ignorePatterns) !== null && _e !== void 0 ? _e : fileConfig.ignorePatterns) !== null && _f !== void 0 ? _f : base.ignorePatterns),
|
|
60
|
+
styleFiles: normalizeStringArray((_h = (_g = overrides.styleFiles) !== null && _g !== void 0 ? _g : fileConfig.styleFiles) !== null && _h !== void 0 ? _h : base.styleFiles),
|
|
61
|
+
minScore: (_k = (_j = overrides.minScore) !== null && _j !== void 0 ? _j : fileConfig.minScore) !== null && _k !== void 0 ? _k : base.minScore
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
exports.mergeConfig = mergeConfig;
|
|
65
|
+
const normalizeStringArray = (value) => {
|
|
66
|
+
if (!Array.isArray(value))
|
|
67
|
+
return [];
|
|
68
|
+
return value.filter((entry) => typeof entry === "string");
|
|
69
|
+
};
|
|
70
|
+
const findConfigUpwards = (startDir) => {
|
|
71
|
+
let current = startDir;
|
|
72
|
+
while (true) {
|
|
73
|
+
for (const name of configFileNames) {
|
|
74
|
+
const candidate = path.join(current, name);
|
|
75
|
+
if (fs.existsSync(candidate))
|
|
76
|
+
return candidate;
|
|
77
|
+
}
|
|
78
|
+
const parent = path.dirname(current);
|
|
79
|
+
if (parent === current)
|
|
80
|
+
break;
|
|
81
|
+
current = parent;
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
};
|
|
85
|
+
const readConfigFile = (configPath) => {
|
|
86
|
+
try {
|
|
87
|
+
const raw = fs.readFileSync(configPath, "utf8");
|
|
88
|
+
const parsed = JSON.parse(raw);
|
|
89
|
+
if (!parsed || typeof parsed !== "object")
|
|
90
|
+
return undefined;
|
|
91
|
+
const record = parsed;
|
|
92
|
+
const minScore = record.minScore;
|
|
93
|
+
return {
|
|
94
|
+
includePatterns: normalizeStringArray(record.includePatterns),
|
|
95
|
+
ignoreDirs: normalizeStringArray(record.ignoreDirs),
|
|
96
|
+
ignorePatterns: normalizeStringArray(record.ignorePatterns),
|
|
97
|
+
styleFiles: normalizeStringArray(record.styleFiles),
|
|
98
|
+
minScore: typeof minScore === "number" ? minScore : undefined
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { A11yLensConfig, A11yLensConfigOverrides } from "../types/config";
|
|
2
|
+
import type { ProjectScanResult } from "../types/results";
|
|
3
|
+
export interface RunOptions {
|
|
4
|
+
cwd?: string;
|
|
5
|
+
configFilePath?: string;
|
|
6
|
+
overrides?: A11yLensConfigOverrides;
|
|
7
|
+
}
|
|
8
|
+
export declare const runA11yLens: (roots: string[], options?: RunOptions) => ProjectScanResult;
|
|
9
|
+
export declare const resolveConfig: (cwd: string, configFilePath?: string, overrides?: A11yLensConfigOverrides) => A11yLensConfig;
|
|
@@ -0,0 +1,158 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.resolveConfig = exports.runA11yLens = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const loader_1 = require("../config/loader");
|
|
39
|
+
const fileWalker_1 = require("../scanner/fileWalker");
|
|
40
|
+
const fileAnalyzer_1 = require("../scanner/fileAnalyzer");
|
|
41
|
+
const templateExtractor_1 = require("../scanner/templateExtractor");
|
|
42
|
+
const styleExtractor_1 = require("../scanner/styleExtractor");
|
|
43
|
+
const ast_1 = require("../parser/ast");
|
|
44
|
+
const engine_1 = require("../rules/engine");
|
|
45
|
+
const registry_1 = require("../rules/registry");
|
|
46
|
+
const styleResolver_1 = require("./styleResolver");
|
|
47
|
+
const severityWeights = {
|
|
48
|
+
error: 5,
|
|
49
|
+
warn: 2,
|
|
50
|
+
info: 1
|
|
51
|
+
};
|
|
52
|
+
const runA11yLens = (roots, options = {}) => {
|
|
53
|
+
var _a, _b, _c, _d, _e;
|
|
54
|
+
const cwd = (_a = options.cwd) !== null && _a !== void 0 ? _a : process.cwd();
|
|
55
|
+
const { config, configPath } = (0, loader_1.loadConfig)(cwd, options.configFilePath, options.overrides);
|
|
56
|
+
const filePaths = (0, fileWalker_1.fileSystemWalker)(roots, (0, fileWalker_1.defaultWalkOptions)(config));
|
|
57
|
+
const filePathSet = new Set(filePaths.map((filePath) => path.resolve(filePath)));
|
|
58
|
+
const issueMap = new Map();
|
|
59
|
+
const styleMap = new Map();
|
|
60
|
+
const baseDir = configPath ? path.dirname(configPath) : cwd;
|
|
61
|
+
const globalStyles = [];
|
|
62
|
+
const addIssues = (filePath, issues) => {
|
|
63
|
+
var _a;
|
|
64
|
+
if (issues.length === 0)
|
|
65
|
+
return;
|
|
66
|
+
const key = path.resolve(filePath);
|
|
67
|
+
const existing = (_a = issueMap.get(key)) !== null && _a !== void 0 ? _a : [];
|
|
68
|
+
existing.push(...issues);
|
|
69
|
+
issueMap.set(key, existing);
|
|
70
|
+
};
|
|
71
|
+
for (const filePath of filePaths) {
|
|
72
|
+
issueMap.set(path.resolve(filePath), []);
|
|
73
|
+
}
|
|
74
|
+
for (const styleFile of (_b = config.styleFiles) !== null && _b !== void 0 ? _b : []) {
|
|
75
|
+
const resolved = path.resolve(baseDir, styleFile);
|
|
76
|
+
const content = (0, fileAnalyzer_1.readFileContent)(resolved);
|
|
77
|
+
if (!content) {
|
|
78
|
+
addIssues(resolved, [
|
|
79
|
+
{
|
|
80
|
+
ruleId: "style-extraction",
|
|
81
|
+
message: `Unable to read style file: ${resolved}`,
|
|
82
|
+
filePath: resolved,
|
|
83
|
+
severity: "warn"
|
|
84
|
+
}
|
|
85
|
+
]);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
globalStyles.push(content);
|
|
89
|
+
}
|
|
90
|
+
for (const filePath of filePaths) {
|
|
91
|
+
if (!filePath.endsWith(".component.ts"))
|
|
92
|
+
continue;
|
|
93
|
+
const content = (0, fileAnalyzer_1.readFileContent)(filePath);
|
|
94
|
+
const extraction = (0, styleExtractor_1.extractStyles)(filePath, content);
|
|
95
|
+
addIssues(filePath, extraction.issues);
|
|
96
|
+
const collected = (_c = styleMap.get(filePath)) !== null && _c !== void 0 ? _c : [];
|
|
97
|
+
for (const style of extraction.styles) {
|
|
98
|
+
collected.push(style.content);
|
|
99
|
+
}
|
|
100
|
+
styleMap.set(filePath, collected);
|
|
101
|
+
}
|
|
102
|
+
for (const filePath of filePaths) {
|
|
103
|
+
const content = (0, fileAnalyzer_1.readFileContent)(filePath);
|
|
104
|
+
const extraction = (0, templateExtractor_1.extractTemplates)(filePath, content);
|
|
105
|
+
addIssues(filePath, extraction.issues);
|
|
106
|
+
for (const template of extraction.templates) {
|
|
107
|
+
if (template.kind === "external" &&
|
|
108
|
+
template.filePath !== filePath &&
|
|
109
|
+
filePathSet.has(path.resolve(template.filePath))) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const templateKey = path.resolve(template.filePath);
|
|
113
|
+
if (!issueMap.has(templateKey)) {
|
|
114
|
+
issueMap.set(templateKey, []);
|
|
115
|
+
}
|
|
116
|
+
const styleSheets = [
|
|
117
|
+
...globalStyles,
|
|
118
|
+
...((_d = styleMap.get(template.originFilePath)) !== null && _d !== void 0 ? _d : [])
|
|
119
|
+
];
|
|
120
|
+
const styleLookup = (0, styleResolver_1.buildStyleLookup)(styleSheets);
|
|
121
|
+
let ast;
|
|
122
|
+
try {
|
|
123
|
+
ast = (0, ast_1.parseTemplate)(template.content);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
addIssues(template.filePath, [
|
|
127
|
+
{
|
|
128
|
+
ruleId: "template-parse",
|
|
129
|
+
message: "Failed to parse template content.",
|
|
130
|
+
filePath: template.filePath,
|
|
131
|
+
severity: "warn"
|
|
132
|
+
}
|
|
133
|
+
]);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const issues = (0, engine_1.runRules)(registry_1.builtInRules, ast, template.filePath, template.content, styleLookup);
|
|
137
|
+
addIssues(template.filePath, issues);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const files = Array.from(issueMap.entries())
|
|
141
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
142
|
+
.map(([filePath, issues]) => ({ filePath, issues }));
|
|
143
|
+
const totals = { error: 0, warn: 0, info: 0 };
|
|
144
|
+
let score = 100;
|
|
145
|
+
for (const { issues } of files) {
|
|
146
|
+
for (const issue of issues) {
|
|
147
|
+
totals[issue.severity] += 1;
|
|
148
|
+
score -= (_e = severityWeights[issue.severity]) !== null && _e !== void 0 ? _e : 0;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
score = Math.max(0, Math.min(100, score));
|
|
152
|
+
return { files, score, totals };
|
|
153
|
+
};
|
|
154
|
+
exports.runA11yLens = runA11yLens;
|
|
155
|
+
const resolveConfig = (cwd, configFilePath, overrides) => {
|
|
156
|
+
return (0, loader_1.loadConfig)(cwd, configFilePath, overrides).config;
|
|
157
|
+
};
|
|
158
|
+
exports.resolveConfig = resolveConfig;
|