react-native-accessibility-scanner 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/LICENSE +21 -0
- package/README.md +501 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +131 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/eslint/index.d.ts +59 -0
- package/dist/eslint/index.d.ts.map +1 -0
- package/dist/eslint/index.js +181 -0
- package/dist/eslint/index.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/reporters/console-reporter.d.ts +5 -0
- package/dist/reporters/console-reporter.d.ts.map +1 -0
- package/dist/reporters/console-reporter.js +67 -0
- package/dist/reporters/console-reporter.js.map +1 -0
- package/dist/reporters/json-reporter.d.ts +5 -0
- package/dist/reporters/json-reporter.d.ts.map +1 -0
- package/dist/reporters/json-reporter.js +13 -0
- package/dist/reporters/json-reporter.js.map +1 -0
- package/dist/reporters/markdown-reporter.d.ts +5 -0
- package/dist/reporters/markdown-reporter.d.ts.map +1 -0
- package/dist/reporters/markdown-reporter.js +66 -0
- package/dist/reporters/markdown-reporter.js.map +1 -0
- package/dist/rules/duplicate-labels.d.ts +25 -0
- package/dist/rules/duplicate-labels.d.ts.map +1 -0
- package/dist/rules/duplicate-labels.js +107 -0
- package/dist/rules/duplicate-labels.js.map +1 -0
- package/dist/rules/index.d.ts +13 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +38 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/missing-hint.d.ts +14 -0
- package/dist/rules/missing-hint.d.ts.map +1 -0
- package/dist/rules/missing-hint.js +94 -0
- package/dist/rules/missing-hint.js.map +1 -0
- package/dist/rules/missing-label.d.ts +10 -0
- package/dist/rules/missing-label.d.ts.map +1 -0
- package/dist/rules/missing-label.js +75 -0
- package/dist/rules/missing-label.js.map +1 -0
- package/dist/rules/missing-role.d.ts +10 -0
- package/dist/rules/missing-role.d.ts.map +1 -0
- package/dist/rules/missing-role.js +82 -0
- package/dist/rules/missing-role.js.map +1 -0
- package/dist/rules/small-touch-target.d.ts +10 -0
- package/dist/rules/small-touch-target.d.ts.map +1 -0
- package/dist/rules/small-touch-target.js +90 -0
- package/dist/rules/small-touch-target.js.map +1 -0
- package/dist/rules/touchable-without-label.d.ts +17 -0
- package/dist/rules/touchable-without-label.d.ts.map +1 -0
- package/dist/rules/touchable-without-label.js +81 -0
- package/dist/rules/touchable-without-label.js.map +1 -0
- package/dist/scanner/config-loader.d.ts +3 -0
- package/dist/scanner/config-loader.d.ts.map +1 -0
- package/dist/scanner/config-loader.js +46 -0
- package/dist/scanner/config-loader.js.map +1 -0
- package/dist/scanner/file-scanner.d.ts +6 -0
- package/dist/scanner/file-scanner.d.ts.map +1 -0
- package/dist/scanner/file-scanner.js +82 -0
- package/dist/scanner/file-scanner.js.map +1 -0
- package/dist/scanner/index.d.ts +12 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +64 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/scanner/scorer.d.ts +26 -0
- package/dist/scanner/scorer.d.ts.map +1 -0
- package/dist/scanner/scorer.js +65 -0
- package/dist/scanner/scorer.js.map +1 -0
- package/dist/types/index.d.ts +88 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +13 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/ast.d.ts +24 -0
- package/dist/utils/ast.d.ts.map +1 -0
- package/dist/utils/ast.js +170 -0
- package/dist/utils/ast.js.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,81 @@
|
|
|
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.TouchableWithoutLabelRule = void 0;
|
|
37
|
+
const t = __importStar(require("@babel/types"));
|
|
38
|
+
const ast_1 = require("../utils/ast");
|
|
39
|
+
/**
|
|
40
|
+
* Detects touchable elements that have neither:
|
|
41
|
+
* - an accessibilityLabel
|
|
42
|
+
* - a direct Text child
|
|
43
|
+
*
|
|
44
|
+
* This means screen readers will have nothing to announce.
|
|
45
|
+
*/
|
|
46
|
+
class TouchableWithoutLabelRule {
|
|
47
|
+
constructor() {
|
|
48
|
+
this.id = "touchable-without-label";
|
|
49
|
+
this.name = "Touchable Without Label or Text";
|
|
50
|
+
this.description = "A touchable with no accessibilityLabel and no Text child cannot be described by screen readers.";
|
|
51
|
+
this.severity = "high";
|
|
52
|
+
}
|
|
53
|
+
run(node, context) {
|
|
54
|
+
if (!t.isJSXElement(node))
|
|
55
|
+
return [];
|
|
56
|
+
const opening = node.openingElement;
|
|
57
|
+
const name = (0, ast_1.getComponentName)(opening);
|
|
58
|
+
if (!name || !ast_1.TOUCHABLE_COMPONENTS.has(name))
|
|
59
|
+
return [];
|
|
60
|
+
const hasLabel = (0, ast_1.hasNonEmptyAttribute)(opening, "accessibilityLabel");
|
|
61
|
+
const hasText = (0, ast_1.hasTextChild)(node);
|
|
62
|
+
if (hasLabel || hasText)
|
|
63
|
+
return [];
|
|
64
|
+
const loc = opening.loc?.start ?? { line: 1, column: 0 };
|
|
65
|
+
return [
|
|
66
|
+
{
|
|
67
|
+
ruleId: this.id,
|
|
68
|
+
ruleName: this.name,
|
|
69
|
+
message: `<${name}> has no accessibilityLabel and no Text child — screen readers cannot describe it.`,
|
|
70
|
+
suggestion: `Add accessibilityLabel="Describe action" OR wrap content in a <Text> component.`,
|
|
71
|
+
severity: this.severity,
|
|
72
|
+
file: context.filePath,
|
|
73
|
+
line: loc.line,
|
|
74
|
+
column: loc.column + 1,
|
|
75
|
+
codeSnippet: (0, ast_1.getSnippet)(context.sourceCode, loc.line),
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.TouchableWithoutLabelRule = TouchableWithoutLabelRule;
|
|
81
|
+
//# sourceMappingURL=touchable-without-label.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"touchable-without-label.js","sourceRoot":"","sources":["../../src/rules/touchable-without-label.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,gDAAkC;AAElC,sCAMsB;AAEtB;;;;;;GAMG;AACH,MAAa,yBAAyB;IAAtC;QACE,OAAE,GAAG,yBAAyB,CAAC;QAC/B,SAAI,GAAG,iCAAiC,CAAC;QACzC,gBAAW,GACT,iGAAiG,CAAC;QACpG,aAAQ,GAAG,MAAe,CAAC;IA6B7B,CAAC;IA3BC,GAAG,CAAC,IAAY,EAAE,OAAoB;QACpC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QAErC,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC;QACpC,MAAM,IAAI,GAAG,IAAA,sBAAgB,EAAC,OAAO,CAAC,CAAC;QACvC,IAAI,CAAC,IAAI,IAAI,CAAC,0BAAoB,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QAExD,MAAM,QAAQ,GAAG,IAAA,0BAAoB,EAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC;QACrE,MAAM,OAAO,GAAG,IAAA,kBAAY,EAAC,IAAI,CAAC,CAAC;QAEnC,IAAI,QAAQ,IAAI,OAAO;YAAE,OAAO,EAAE,CAAC;QAEnC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,KAAK,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QACzD,OAAO;YACL;gBACE,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,QAAQ,EAAE,IAAI,CAAC,IAAI;gBACnB,OAAO,EAAE,IAAI,IAAI,oFAAoF;gBACrG,UAAU,EAAE,iFAAiF;gBAC7F,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,IAAI,EAAE,OAAO,CAAC,QAAQ;gBACtB,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,MAAM,EAAE,GAAG,CAAC,MAAM,GAAG,CAAC;gBACtB,WAAW,EAAE,IAAA,gBAAU,EAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,IAAI,CAAC;aACtD;SACF,CAAC;IACJ,CAAC;CACF;AAlCD,8DAkCC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/scanner/config-loader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAK3D,wBAAgB,UAAU,CAAC,OAAO,GAAE,WAAgB,GAAG,aAAa,CA0BnE"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadConfig = loadConfig;
|
|
4
|
+
const cosmiconfig_1 = require("cosmiconfig");
|
|
5
|
+
const types_1 = require("../types");
|
|
6
|
+
const MODULE_NAME = "accessibility-scanner";
|
|
7
|
+
function loadConfig(options = {}) {
|
|
8
|
+
if (options.config) {
|
|
9
|
+
return mergeConfig(types_1.DEFAULT_CONFIG, options);
|
|
10
|
+
}
|
|
11
|
+
const explorer = (0, cosmiconfig_1.cosmiconfigSync)(MODULE_NAME, {
|
|
12
|
+
searchPlaces: [
|
|
13
|
+
`${MODULE_NAME}.config.js`,
|
|
14
|
+
`${MODULE_NAME}.config.cjs`,
|
|
15
|
+
`.${MODULE_NAME}rc`,
|
|
16
|
+
`.${MODULE_NAME}rc.json`,
|
|
17
|
+
"package.json",
|
|
18
|
+
],
|
|
19
|
+
});
|
|
20
|
+
let fileConfig = {};
|
|
21
|
+
try {
|
|
22
|
+
const result = explorer.search();
|
|
23
|
+
if (result && !result.isEmpty) {
|
|
24
|
+
fileConfig = result.config;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// No config file found — use defaults
|
|
29
|
+
}
|
|
30
|
+
return mergeConfig(types_1.DEFAULT_CONFIG, { ...fileConfig, ...options });
|
|
31
|
+
}
|
|
32
|
+
function mergeConfig(base, overrides) {
|
|
33
|
+
const touchTarget = {
|
|
34
|
+
minWidth: overrides.touchTarget?.minWidth ?? base.touchTarget.minWidth,
|
|
35
|
+
minHeight: overrides.touchTarget?.minHeight ?? base.touchTarget.minHeight,
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
path: overrides.path ?? base.path,
|
|
39
|
+
ignore: overrides.ignore ?? base.ignore,
|
|
40
|
+
failOnHigh: overrides.failOnHigh ?? base.failOnHigh,
|
|
41
|
+
failOnMedium: overrides.failOnMedium ?? base.failOnMedium,
|
|
42
|
+
touchTarget,
|
|
43
|
+
disabledRules: overrides.disabledRules ?? base.disabledRules,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=config-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../../src/scanner/config-loader.ts"],"names":[],"mappings":";;AAMA,gCA0BC;AAhCD,6CAA8C;AAE9C,oCAA0C;AAE1C,MAAM,WAAW,GAAG,uBAAuB,CAAC;AAE5C,SAAgB,UAAU,CAAC,UAAuB,EAAE;IAClD,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,OAAO,WAAW,CAAC,sBAAc,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,QAAQ,GAAG,IAAA,6BAAe,EAAC,WAAW,EAAE;QAC5C,YAAY,EAAE;YACZ,GAAG,WAAW,YAAY;YAC1B,GAAG,WAAW,aAAa;YAC3B,IAAI,WAAW,IAAI;YACnB,IAAI,WAAW,SAAS;YACxB,cAAc;SACf;KACF,CAAC,CAAC;IAEH,IAAI,UAAU,GAA2B,EAAE,CAAC;IAC5C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;QACjC,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAC9B,UAAU,GAAG,MAAM,CAAC,MAAgC,CAAC;QACvD,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,sCAAsC;IACxC,CAAC;IAED,OAAO,WAAW,CAAC,sBAAc,EAAE,EAAE,GAAG,UAAU,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;AACpE,CAAC;AAED,SAAS,WAAW,CAAC,IAAmB,EAAE,SAA+B;IACvE,MAAM,WAAW,GAAG;QAClB,QAAQ,EAAE,SAAS,CAAC,WAAW,EAAE,QAAQ,IAAI,IAAI,CAAC,WAAW,CAAC,QAAQ;QACtE,SAAS,EAAE,SAAS,CAAC,WAAW,EAAE,SAAS,IAAI,IAAI,CAAC,WAAW,CAAC,SAAS;KAC1E,CAAC;IACF,OAAO;QACL,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI;QACjC,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM;QACvC,UAAU,EAAE,SAAS,CAAC,UAAU,IAAI,IAAI,CAAC,UAAU;QACnD,YAAY,EAAE,SAAS,CAAC,YAAY,IAAI,IAAI,CAAC,YAAY;QACzD,WAAW;QACX,aAAa,EAAE,SAAS,CAAC,aAAa,IAAI,IAAI,CAAC,aAAa;KAC7D,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FileScanResult, ScannerConfig } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Parses a single TSX/JSX/TS/JS file and runs all registered accessibility rules.
|
|
4
|
+
*/
|
|
5
|
+
export declare function scanFile(filePath: string, sourceCode: string, config: ScannerConfig): FileScanResult;
|
|
6
|
+
//# sourceMappingURL=file-scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-scanner.d.ts","sourceRoot":"","sources":["../../src/scanner/file-scanner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAsB,cAAc,EAAe,aAAa,EAAE,MAAM,UAAU,CAAC;AAG/F;;GAEG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,cAAc,CAuCpG"}
|
|
@@ -0,0 +1,82 @@
|
|
|
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.scanFile = scanFile;
|
|
40
|
+
const parser = __importStar(require("@babel/parser"));
|
|
41
|
+
const traverse_1 = __importDefault(require("@babel/traverse"));
|
|
42
|
+
const rules_1 = require("../rules");
|
|
43
|
+
/**
|
|
44
|
+
* Parses a single TSX/JSX/TS/JS file and runs all registered accessibility rules.
|
|
45
|
+
*/
|
|
46
|
+
function scanFile(filePath, sourceCode, config) {
|
|
47
|
+
const context = { filePath, sourceCode, config };
|
|
48
|
+
const rules = (0, rules_1.getAllRules)().filter((r) => !config.disabledRules.includes(r.id));
|
|
49
|
+
// Reset stateful rules for this file
|
|
50
|
+
rules_1.duplicateLabelsRule.reset();
|
|
51
|
+
let ast;
|
|
52
|
+
try {
|
|
53
|
+
ast = parser.parse(sourceCode, {
|
|
54
|
+
sourceType: "module",
|
|
55
|
+
plugins: ["jsx", "typescript", "decorators-legacy", "classProperties"],
|
|
56
|
+
errorRecovery: true,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// If parsing fails completely, skip the file silently
|
|
61
|
+
return { filePath, issues: [] };
|
|
62
|
+
}
|
|
63
|
+
const issues = [];
|
|
64
|
+
(0, traverse_1.default)(ast, {
|
|
65
|
+
enter(path) {
|
|
66
|
+
for (const rule of rules) {
|
|
67
|
+
try {
|
|
68
|
+
const ruleIssues = rule.run(path.node, context);
|
|
69
|
+
issues.push(...ruleIssues);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Swallow per-rule errors — don't let one rule crash the whole scan
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
// Collect issues from post-traversal (file-level) rules
|
|
78
|
+
const duplicateIssues = rules_1.duplicateLabelsRule.collectIssues(context);
|
|
79
|
+
issues.push(...duplicateIssues);
|
|
80
|
+
return { filePath, issues };
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=file-scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-scanner.js","sourceRoot":"","sources":["../../src/scanner/file-scanner.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAQA,4BAuCC;AA/CD,sDAAwC;AACxC,+DAAuC;AAEvC,oCAA4D;AAE5D;;GAEG;AACH,SAAgB,QAAQ,CAAC,QAAgB,EAAE,UAAkB,EAAE,MAAqB;IAClF,MAAM,OAAO,GAAgB,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;IAC9D,MAAM,KAAK,GAAG,IAAA,mBAAW,GAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAEhF,qCAAqC;IACrC,2BAAmB,CAAC,KAAK,EAAE,CAAC;IAE5B,IAAI,GAAoC,CAAC;IACzC,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE;YAC7B,UAAU,EAAE,QAAQ;YACpB,OAAO,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,iBAAiB,CAAC;YACtE,aAAa,EAAE,IAAI;SACpB,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,sDAAsD;QACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAClC,CAAC;IAED,MAAM,MAAM,GAAyB,EAAE,CAAC;IAExC,IAAA,kBAAQ,EAAC,GAAG,EAAE;QACZ,KAAK,CAAC,IAAI;YACR,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,CAAC;oBACH,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;oBAChD,MAAM,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,CAAC;gBAC7B,CAAC;gBAAC,MAAM,CAAC;oBACP,oEAAoE;gBACtE,CAAC;YACH,CAAC;QACH,CAAC;KACF,CAAC,CAAC;IAEH,wDAAwD;IACxD,MAAM,eAAe,GAAG,2BAAmB,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IACnE,MAAM,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,CAAC;IAEhC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ScanReport, ScanOptions } from "../types";
|
|
2
|
+
export declare class AccessibilityScanner {
|
|
3
|
+
/**
|
|
4
|
+
* Scan a React Native project for accessibility issues.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const report = await AccessibilityScanner.scan({ path: "./src" });
|
|
8
|
+
* console.log(report.summary.totalIssues);
|
|
9
|
+
*/
|
|
10
|
+
static scan(options?: ScanOptions): Promise<ScanReport>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/scanner/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAe,WAAW,EAAE,MAAM,UAAU,CAAC;AAMrE,qBAAa,oBAAoB;IAC/B;;;;;;OAMG;WACU,IAAI,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;CA+ClE"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AccessibilityScanner = void 0;
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const glob_1 = require("glob");
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const config_loader_1 = require("./config-loader");
|
|
11
|
+
const file_scanner_1 = require("./file-scanner");
|
|
12
|
+
const SUPPORTED_EXTENSIONS = ["tsx", "ts", "jsx", "js"];
|
|
13
|
+
class AccessibilityScanner {
|
|
14
|
+
/**
|
|
15
|
+
* Scan a React Native project for accessibility issues.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* const report = await AccessibilityScanner.scan({ path: "./src" });
|
|
19
|
+
* console.log(report.summary.totalIssues);
|
|
20
|
+
*/
|
|
21
|
+
static async scan(options = {}) {
|
|
22
|
+
const config = (0, config_loader_1.loadConfig)(options);
|
|
23
|
+
const startTime = Date.now();
|
|
24
|
+
// Resolve source paths
|
|
25
|
+
const paths = Array.isArray(config.path) ? config.path : [config.path];
|
|
26
|
+
// Collect all matching files
|
|
27
|
+
const allFiles = new Set();
|
|
28
|
+
for (const srcPath of paths) {
|
|
29
|
+
const resolved = path_1.default.resolve(srcPath);
|
|
30
|
+
const pattern = `${resolved}/**/*.{${SUPPORTED_EXTENSIONS.join(",")}}`;
|
|
31
|
+
const matches = await (0, glob_1.glob)(pattern, {
|
|
32
|
+
ignore: config.ignore,
|
|
33
|
+
absolute: true,
|
|
34
|
+
});
|
|
35
|
+
matches.forEach((f) => allFiles.add(f));
|
|
36
|
+
}
|
|
37
|
+
const fileList = Array.from(allFiles);
|
|
38
|
+
// Scan each file
|
|
39
|
+
const results = fileList.map((filePath) => {
|
|
40
|
+
let source = "";
|
|
41
|
+
try {
|
|
42
|
+
source = (0, fs_1.readFileSync)(filePath, "utf-8");
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return { filePath, issues: [] };
|
|
46
|
+
}
|
|
47
|
+
return (0, file_scanner_1.scanFile)(filePath, source, config);
|
|
48
|
+
});
|
|
49
|
+
// Build flat issue list
|
|
50
|
+
const issues = results.flatMap((r) => r.issues);
|
|
51
|
+
const summary = {
|
|
52
|
+
totalFiles: fileList.length,
|
|
53
|
+
scannedFiles: fileList.length,
|
|
54
|
+
totalIssues: issues.length,
|
|
55
|
+
highIssues: issues.filter((i) => i.severity === "high").length,
|
|
56
|
+
mediumIssues: issues.filter((i) => i.severity === "medium").length,
|
|
57
|
+
lowIssues: issues.filter((i) => i.severity === "low").length,
|
|
58
|
+
durationMs: Date.now() - startTime,
|
|
59
|
+
};
|
|
60
|
+
return { summary, results, issues };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.AccessibilityScanner = AccessibilityScanner;
|
|
64
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/scanner/index.ts"],"names":[],"mappings":";;;;;;AAAA,2BAAkC;AAClC,+BAA4B;AAC5B,gDAAwB;AAExB,mDAA6C;AAC7C,iDAA0C;AAE1C,MAAM,oBAAoB,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;AAExD,MAAa,oBAAoB;IAC/B;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,UAAuB,EAAE;QACzC,MAAM,MAAM,GAAG,IAAA,0BAAU,EAAC,OAAO,CAAC,CAAC;QACnC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE7B,uBAAuB;QACvB,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAEvE,6BAA6B;QAC7B,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;QACnC,KAAK,MAAM,OAAO,IAAI,KAAK,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAG,cAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YACvC,MAAM,OAAO,GAAG,GAAG,QAAQ,UAAU,oBAAoB,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YACvE,MAAM,OAAO,GAAG,MAAM,IAAA,WAAI,EAAC,OAAO,EAAE;gBAClC,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,QAAQ,EAAE,IAAI;aACf,CAAC,CAAC;YACH,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1C,CAAC;QAED,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAEtC,iBAAiB;QACjB,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;YACxC,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,CAAC;gBACH,MAAM,GAAG,IAAA,iBAAY,EAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAC3C,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;YAClC,CAAC;YACD,OAAO,IAAA,uBAAQ,EAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;QAEH,wBAAwB;QACxB,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAEhD,MAAM,OAAO,GAAgB;YAC3B,UAAU,EAAE,QAAQ,CAAC,MAAM;YAC3B,YAAY,EAAE,QAAQ,CAAC,MAAM;YAC7B,WAAW,EAAE,MAAM,CAAC,MAAM;YAC1B,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,MAAM;YAC9D,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,MAAM;YAClE,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,KAAK,CAAC,CAAC,MAAM;YAC5D,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;SACnC,CAAC;QAEF,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IACtC,CAAC;CACF;AAvDD,oDAuDC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ScanReport, FileScanResult } from "../types";
|
|
2
|
+
export interface ScreenScore {
|
|
3
|
+
file: string;
|
|
4
|
+
/** Short filename for display (e.g. HomeScreen.tsx) */
|
|
5
|
+
displayName: string;
|
|
6
|
+
score: number;
|
|
7
|
+
issueCount: number;
|
|
8
|
+
highIssues: number;
|
|
9
|
+
mediumIssues: number;
|
|
10
|
+
lowIssues: number;
|
|
11
|
+
}
|
|
12
|
+
export interface AccessibilityScore {
|
|
13
|
+
overall: number;
|
|
14
|
+
screens: ScreenScore[];
|
|
15
|
+
/** Breakdown by category (rule dimension) */
|
|
16
|
+
breakdown: {
|
|
17
|
+
labels: number;
|
|
18
|
+
roles: number;
|
|
19
|
+
touchTargets: number;
|
|
20
|
+
hints: number;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export declare function scoreFile(result: FileScanResult): ScreenScore;
|
|
24
|
+
export declare function computeScore(report: ScanReport): AccessibilityScore;
|
|
25
|
+
export declare function scoreEmoji(score: number): string;
|
|
26
|
+
//# sourceMappingURL=scorer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scorer.d.ts","sourceRoot":"","sources":["../../src/scanner/scorer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAY,MAAM,UAAU,CAAC;AAUrE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,6CAA6C;IAC7C,SAAS,EAAE;QACT,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,YAAY,EAAE,MAAM,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAQD,wBAAgB,SAAS,CAAC,MAAM,EAAE,cAAc,GAAG,WAAW,CAsB7D;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,kBAAkB,CA0BnE;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKhD"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.scoreFile = scoreFile;
|
|
4
|
+
exports.computeScore = computeScore;
|
|
5
|
+
exports.scoreEmoji = scoreEmoji;
|
|
6
|
+
// ─── Scoring weights ──────────────────────────────────────────────────────────
|
|
7
|
+
const SEVERITY_PENALTY = {
|
|
8
|
+
high: 20,
|
|
9
|
+
medium: 8,
|
|
10
|
+
low: 3,
|
|
11
|
+
};
|
|
12
|
+
// ─── Score calculation ────────────────────────────────────────────────────────
|
|
13
|
+
function clamp(val) {
|
|
14
|
+
return Math.max(0, Math.min(100, Math.round(val)));
|
|
15
|
+
}
|
|
16
|
+
function scoreFile(result) {
|
|
17
|
+
const { filePath, issues } = result;
|
|
18
|
+
const displayName = filePath.split("/").pop() ?? filePath;
|
|
19
|
+
const highIssues = issues.filter((i) => i.severity === "high").length;
|
|
20
|
+
const mediumIssues = issues.filter((i) => i.severity === "medium").length;
|
|
21
|
+
const lowIssues = issues.filter((i) => i.severity === "low").length;
|
|
22
|
+
const penalty = highIssues * SEVERITY_PENALTY.high +
|
|
23
|
+
mediumIssues * SEVERITY_PENALTY.medium +
|
|
24
|
+
lowIssues * SEVERITY_PENALTY.low;
|
|
25
|
+
return {
|
|
26
|
+
file: filePath,
|
|
27
|
+
displayName,
|
|
28
|
+
score: clamp(100 - penalty),
|
|
29
|
+
issueCount: issues.length,
|
|
30
|
+
highIssues,
|
|
31
|
+
mediumIssues,
|
|
32
|
+
lowIssues,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function computeScore(report) {
|
|
36
|
+
const { results, issues } = report;
|
|
37
|
+
// Per-screen scores (only files that had issues, plus all files)
|
|
38
|
+
const screens = results.map(scoreFile);
|
|
39
|
+
// Overall = average of all screen scores (files with no issues score 100)
|
|
40
|
+
const overall = screens.length === 0
|
|
41
|
+
? 100
|
|
42
|
+
: clamp(screens.reduce((sum, s) => sum + s.score, 0) / screens.length);
|
|
43
|
+
// Category breakdown — penalise each category independently
|
|
44
|
+
const labelIssues = issues.filter((i) => ["missing-label", "touchable-without-label"].includes(i.ruleId)).length;
|
|
45
|
+
const roleIssues = issues.filter((i) => i.ruleId === "missing-role").length;
|
|
46
|
+
const touchIssues = issues.filter((i) => i.ruleId === "small-touch-target").length;
|
|
47
|
+
const hintIssues = issues.filter((i) => ["missing-hint", "duplicate-labels"].includes(i.ruleId)).length;
|
|
48
|
+
const breakdown = {
|
|
49
|
+
labels: clamp(100 - labelIssues * SEVERITY_PENALTY.high),
|
|
50
|
+
roles: clamp(100 - roleIssues * SEVERITY_PENALTY.medium),
|
|
51
|
+
touchTargets: clamp(100 - touchIssues * SEVERITY_PENALTY.medium),
|
|
52
|
+
hints: clamp(100 - hintIssues * SEVERITY_PENALTY.low),
|
|
53
|
+
};
|
|
54
|
+
return { overall, screens, breakdown };
|
|
55
|
+
}
|
|
56
|
+
function scoreEmoji(score) {
|
|
57
|
+
if (score >= 90)
|
|
58
|
+
return "🟢";
|
|
59
|
+
if (score >= 70)
|
|
60
|
+
return "🟡";
|
|
61
|
+
if (score >= 50)
|
|
62
|
+
return "🟠";
|
|
63
|
+
return "🔴";
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=scorer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scorer.js","sourceRoot":"","sources":["../../src/scanner/scorer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export type Severity = "low" | "medium" | "high";
|
|
2
|
+
export interface AccessibilityIssue {
|
|
3
|
+
/** Rule that produced this issue */
|
|
4
|
+
ruleId: string;
|
|
5
|
+
/** Short human-readable rule name */
|
|
6
|
+
ruleName: string;
|
|
7
|
+
/** Full description of what's wrong */
|
|
8
|
+
message: string;
|
|
9
|
+
/** Actionable suggestion shown to the developer */
|
|
10
|
+
suggestion: string;
|
|
11
|
+
severity: Severity;
|
|
12
|
+
/** Absolute or relative file path */
|
|
13
|
+
file: string;
|
|
14
|
+
/** 1-based line number */
|
|
15
|
+
line: number;
|
|
16
|
+
/** 1-based column number */
|
|
17
|
+
column: number;
|
|
18
|
+
/** The source snippet around the issue */
|
|
19
|
+
codeSnippet?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface RuleContext {
|
|
22
|
+
/** Absolute file path */
|
|
23
|
+
filePath: string;
|
|
24
|
+
/** Raw source code of the file */
|
|
25
|
+
sourceCode: string;
|
|
26
|
+
/** Resolved scanner config */
|
|
27
|
+
config: ScannerConfig;
|
|
28
|
+
}
|
|
29
|
+
export interface AccessibilityRule {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
description: string;
|
|
33
|
+
severity: Severity;
|
|
34
|
+
/** Given an AST node and context, return any issues found */
|
|
35
|
+
run(node: import("@babel/types").Node, context: RuleContext): AccessibilityIssue[];
|
|
36
|
+
}
|
|
37
|
+
export interface TouchTargetConfig {
|
|
38
|
+
minWidth: number;
|
|
39
|
+
minHeight: number;
|
|
40
|
+
}
|
|
41
|
+
export interface ScannerConfig {
|
|
42
|
+
/** Source path(s) to scan */
|
|
43
|
+
path: string | string[];
|
|
44
|
+
/** Glob patterns to ignore */
|
|
45
|
+
ignore: string[];
|
|
46
|
+
/** Exit code 1 when high severity issues found (CLI) */
|
|
47
|
+
failOnHigh: boolean;
|
|
48
|
+
/** Exit code 1 when medium or higher severity issues found (CLI) */
|
|
49
|
+
failOnMedium: boolean;
|
|
50
|
+
/** Platform-specific touch target sizes */
|
|
51
|
+
touchTarget: TouchTargetConfig;
|
|
52
|
+
/** Rule IDs to disable */
|
|
53
|
+
disabledRules: string[];
|
|
54
|
+
}
|
|
55
|
+
export declare const DEFAULT_CONFIG: ScannerConfig;
|
|
56
|
+
export interface FileScanResult {
|
|
57
|
+
filePath: string;
|
|
58
|
+
issues: AccessibilityIssue[];
|
|
59
|
+
}
|
|
60
|
+
export interface ScanSummary {
|
|
61
|
+
totalFiles: number;
|
|
62
|
+
scannedFiles: number;
|
|
63
|
+
totalIssues: number;
|
|
64
|
+
highIssues: number;
|
|
65
|
+
mediumIssues: number;
|
|
66
|
+
lowIssues: number;
|
|
67
|
+
durationMs: number;
|
|
68
|
+
}
|
|
69
|
+
export interface ScanReport {
|
|
70
|
+
summary: ScanSummary;
|
|
71
|
+
results: FileScanResult[];
|
|
72
|
+
/** All issues flattened for convenience */
|
|
73
|
+
issues: AccessibilityIssue[];
|
|
74
|
+
}
|
|
75
|
+
export interface Reporter {
|
|
76
|
+
report(scanReport: ScanReport): string | void;
|
|
77
|
+
}
|
|
78
|
+
export interface ScanOptions {
|
|
79
|
+
path?: string | string[];
|
|
80
|
+
ignore?: string[];
|
|
81
|
+
failOnHigh?: boolean;
|
|
82
|
+
failOnMedium?: boolean;
|
|
83
|
+
touchTarget?: Partial<TouchTargetConfig>;
|
|
84
|
+
disabledRules?: string[];
|
|
85
|
+
/** Override config file loading */
|
|
86
|
+
config?: Partial<ScannerConfig>;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─── Severity ─────────────────────────────────────────────────────────────────
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.DEFAULT_CONFIG = void 0;
|
|
5
|
+
exports.DEFAULT_CONFIG = {
|
|
6
|
+
path: "./src",
|
|
7
|
+
ignore: ["**/*.stories.tsx", "**/*.stories.ts", "**/*.test.tsx", "**/*.test.ts", "**/node_modules/**"],
|
|
8
|
+
failOnHigh: false,
|
|
9
|
+
failOnMedium: false,
|
|
10
|
+
touchTarget: { minWidth: 44, minHeight: 44 },
|
|
11
|
+
disabledRules: [],
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":";AAAA,iFAAiF;;;AAoEpE,QAAA,cAAc,GAAkB;IAC3C,IAAI,EAAE,OAAO;IACb,MAAM,EAAE,CAAC,kBAAkB,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,oBAAoB,CAAC;IACtG,UAAU,EAAE,KAAK;IACjB,YAAY,EAAE,KAAK;IACnB,WAAW,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE;IAC5C,aAAa,EAAE,EAAE;CAClB,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as t from "@babel/types";
|
|
2
|
+
/** Interactive RN component names that must have accessibility props */
|
|
3
|
+
export declare const INTERACTIVE_COMPONENTS: Set<string>;
|
|
4
|
+
/** Components where touch-target rules apply */
|
|
5
|
+
export declare const TOUCHABLE_COMPONENTS: Set<string>;
|
|
6
|
+
/** Critical action keywords that should have accessibilityHint */
|
|
7
|
+
export declare const CRITICAL_ACTION_KEYWORDS: string[];
|
|
8
|
+
/** Get the component name from a JSXOpeningElement */
|
|
9
|
+
export declare function getComponentName(node: t.JSXOpeningElement): string | null;
|
|
10
|
+
/** Return all JSX attributes from an opening element */
|
|
11
|
+
export declare function getJSXAttributes(node: t.JSXOpeningElement): t.JSXAttribute[];
|
|
12
|
+
/** Find a specific named attribute, or return undefined */
|
|
13
|
+
export declare function findAttribute(node: t.JSXOpeningElement, attrName: string): t.JSXAttribute | undefined;
|
|
14
|
+
/** Check if an attribute is present AND has a non-empty value */
|
|
15
|
+
export declare function hasNonEmptyAttribute(node: t.JSXOpeningElement, attrName: string): boolean;
|
|
16
|
+
/** Get the string value of an attribute, or null if dynamic/missing */
|
|
17
|
+
export declare function getStringAttributeValue(node: t.JSXOpeningElement, attrName: string): string | null;
|
|
18
|
+
/** Check if a JSXElement has any direct Text children */
|
|
19
|
+
export declare function hasTextChild(node: t.JSXElement): boolean;
|
|
20
|
+
/** Extract a style value (width/height) from a style prop — best-effort static analysis */
|
|
21
|
+
export declare function extractStyleDimension(node: t.JSXOpeningElement, dimension: "width" | "height"): number | null;
|
|
22
|
+
/** Get a short code snippet for an issue (the line of source) */
|
|
23
|
+
export declare function getSnippet(source: string, line: number): string;
|
|
24
|
+
//# sourceMappingURL=ast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../../src/utils/ast.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,cAAc,CAAC;AAElC,wEAAwE;AACxE,eAAO,MAAM,sBAAsB,aAOjC,CAAC;AAEH,gDAAgD;AAChD,eAAO,MAAM,oBAAoB,aAM/B,CAAC;AAEH,kEAAkE;AAClE,eAAO,MAAM,wBAAwB,UAWpC,CAAC;AAIF,sDAAsD;AACtD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC,iBAAiB,GAAG,MAAM,GAAG,IAAI,CAOzE;AAED,wDAAwD;AACxD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC,iBAAiB,GAAG,CAAC,CAAC,YAAY,EAAE,CAE5E;AAED,2DAA2D;AAC3D,wBAAgB,aAAa,CAC3B,IAAI,EAAE,CAAC,CAAC,iBAAiB,EACzB,QAAQ,EAAE,MAAM,GACf,CAAC,CAAC,YAAY,GAAG,SAAS,CAI5B;AAED,iEAAiE;AACjE,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,CAAC,CAAC,iBAAiB,EACzB,QAAQ,EAAE,MAAM,GACf,OAAO,CAiBT;AAED,uEAAuE;AACvE,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,CAAC,CAAC,iBAAiB,EACzB,QAAQ,EAAE,MAAM,GACf,MAAM,GAAG,IAAI,CAWf;AAED,yDAAyD;AACzD,wBAAgB,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,UAAU,GAAG,OAAO,CASxD;AAED,2FAA2F;AAC3F,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,CAAC,CAAC,iBAAiB,EACzB,SAAS,EAAE,OAAO,GAAG,QAAQ,GAC5B,MAAM,GAAG,IAAI,CAyBf;AAED,iEAAiE;AACjE,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/D"}
|