New: Add `chisel` rule
This commit is contained in:
Родитель
e85f68838d
Коммит
fbeaf8dce2
|
@ -0,0 +1,7 @@
|
|||
@mskeros:registry=https://mseng.pkgs.visualstudio.com/_packaging/Keros/npm/registry/
|
||||
always-auth=true
|
||||
; Treat this auth token like a password. Do not share it with anyone, including Microsoft support. This token expires on or before 3/13/2018.
|
||||
; begin auth token
|
||||
//mseng.pkgs.visualstudio.com/_packaging/Keros/npm/registry/:_authToken=${AUTH_TOKEN}
|
||||
//mseng.pkgs.visualstudio.com/_packaging/Keros/npm/:_authToken=${AUTH_TOKEN}
|
||||
; end auth token
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"connector": {
|
||||
"name": "chrome",
|
||||
"options": {
|
||||
"waitFor": 1000
|
||||
}
|
||||
},
|
||||
"formatters": "stylish",
|
||||
"rulesTimeout": 120000,
|
||||
"rules": {
|
||||
"chisel": "warning"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
# chisel (`chisel`)
|
||||
|
||||
Use Chisel to assess the accessibility of a web site or web application.
|
||||
|
||||
## Can the rule be configured?
|
||||
|
||||
Yes, you can configure this rule in your `sonarwhal` configuration file as
|
||||
follows:
|
||||
|
||||
```json
|
||||
"rules": {
|
||||
"chisel": ["warning", { "testsToRun": "color-contrast"}]
|
||||
}
|
||||
```
|
||||
|
||||
The available parameters include:
|
||||
|
||||
* `testsToRun?`: `string[]` - (Optional) Used to set the rules that will be run
|
||||
in chisel test run.
|
||||
|
||||
* `dom?`: `NodeSelector & Node | NodeList` - (Optional) Used to set the elements
|
||||
that will be tested; defaults to document if dom, selector, and
|
||||
include/exclude are not set.
|
||||
|
||||
* `selector?`: `string` - (Optional) Uses selector to get the elements that will
|
||||
be tested; ignored if dom is set.
|
||||
|
||||
* `include?`: `string[]` - (Optional) Uses an array of selectors to get the
|
||||
elements that will be tested and can be used with exclude; ignored if dom or
|
||||
selector are set.
|
||||
|
||||
* `exclude?`: `string[]` - (Optional) Uses an array of selectors to get the
|
||||
elements that will not be tested and can be used with include; ignored if
|
||||
dom or selector are set.
|
||||
|
||||
* `enableBestPracticeRules?`: `boolean` - (Optional, default is `false`) Enables
|
||||
rule that are not mapped to MAS/considered best practice; default is false
|
||||
and ignored if testsToRun is set.
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"author": "",
|
||||
"ava": {
|
||||
"babel": {
|
||||
"presets": []
|
||||
},
|
||||
"concurrency": 5,
|
||||
"failFast": false,
|
||||
"files": [
|
||||
"dist/tests/**/*.js"
|
||||
],
|
||||
"timeout": "1m"
|
||||
},
|
||||
"description": "Use Chisel to assess the accessibility of web sites and web applications",
|
||||
"devDependencies": {
|
||||
"@types/debug": "0.0.30",
|
||||
"@types/node": "8.0.14",
|
||||
"async-retry": "^1.1.4",
|
||||
"ava": "^0.24.0",
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-plugin-istanbul": "^4.1.5",
|
||||
"babel-preset-es2017": "^6.22.0",
|
||||
"babel-register": "^6.26.0",
|
||||
"cpx": "^1.5.0",
|
||||
"eslint": "^4.15.0",
|
||||
"eslint-plugin-markdown": "^1.0.0-beta.6",
|
||||
"eslint-plugin-typescript": "^0.8.1",
|
||||
"express": "^4.16.2",
|
||||
"markdownlint-cli": "^0.6.0",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"on-headers": "^1.0.1",
|
||||
"rimraf": "^2.6.2",
|
||||
"sonarwhal": "^0.22.1",
|
||||
"typescript": "^2.6.2",
|
||||
"typescript-eslint-parser": "^12.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"rule",
|
||||
"sonarwhal"
|
||||
],
|
||||
"main": "dist/src/index.js",
|
||||
"name": "@sonarwhal/rule-chisel",
|
||||
"scripts": {
|
||||
"build": "npm run clean && npm-run-all build:*",
|
||||
"build:assets": "cpx \"./{src,tests}/**/{!(*.ts),.!(ts)}\" dist",
|
||||
"build:ts": "tsc --outDir dist --rootDir .",
|
||||
"clean": "rimraf dist",
|
||||
"test": "npm run lint && npm run build && ava",
|
||||
"lint": "npm-run-all lint:*",
|
||||
"lint:js": "eslint --ext md --ext ts --ignore-pattern dist .",
|
||||
"lint:md": "markdownlint README.md",
|
||||
"watch:ts": "npm run build:ts -- --watch",
|
||||
"init": "npm install && npm run build",
|
||||
"sonarwhal": "node node_modules/sonarwhal/dist/src/bin/sonarwhal.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"sonarwhal": "^0.22.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mskeros/chisel": "^1.127.2"
|
||||
},
|
||||
"repository": "https://github.com/Microsoft/sonarwhal-microsoft-rules",
|
||||
"version": "0.1.0"
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
/**
|
||||
* @fileoverview Use Chisel to assess the accessibility of a web site or web application.
|
||||
*/
|
||||
|
||||
import { Category } from 'sonarwhal/dist/src/lib/enums/category';
|
||||
import { RuleContext } from 'sonarwhal/dist/src/lib/rule-context';
|
||||
import { IRule, IRuleBuilder, ITraverseEnd, IAsyncHTMLElement, Severity } from 'sonarwhal/dist/src/lib/types';
|
||||
import { debug as d } from 'sonarwhal/dist/src/lib/utils/debug';
|
||||
import { IChiselResults, AxeNodeResult, AxeRule, IChiselDecorations, IChiselOptions } from '@mskeros/chisel';
|
||||
import { readFileAsync } from 'sonarwhal/dist/src/lib/utils/misc';
|
||||
|
||||
const debug: debug.IDebugger = d(__filename);
|
||||
|
||||
/*
|
||||
* ------------------------------------------------------------------------------
|
||||
* Public
|
||||
* ------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
const rule: IRuleBuilder = {
|
||||
create(context: RuleContext): IRule {
|
||||
/** Configuration of the chisel rule. */
|
||||
let chiselConfig: IChiselOptions = {};
|
||||
|
||||
/** Table of overriden config properties. */
|
||||
const overrideLookUp: object = {
|
||||
dom: ['include', 'exclude'],
|
||||
selector: ['include', 'exclude'],
|
||||
testsToRun: ['enableBestPracticeRules']
|
||||
};
|
||||
|
||||
/** Interface of a chisel violation. */
|
||||
interface IChiselViolation extends AxeRule, IChiselDecorations { }
|
||||
|
||||
/** Load rule configuration. */
|
||||
const loadRuleConfig = () => {
|
||||
if (!context.ruleOptions) {
|
||||
return;
|
||||
}
|
||||
|
||||
chiselConfig = context.ruleOptions;
|
||||
};
|
||||
|
||||
/** Generate the script to initiate the chisel scan in a page. */
|
||||
const generateScript = (): string => {
|
||||
// This is run in the page, not sonarwhal itself.
|
||||
const script: string =
|
||||
`function runChiselScan() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
Chisel.scan(${JSON.stringify(chiselConfig, null, 2)}, function (results) {
|
||||
resolve(results);
|
||||
}, function (error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}`;
|
||||
|
||||
return script;
|
||||
};
|
||||
|
||||
/** Get element from an axe node in the scan result. */
|
||||
const getElement = async (node: AxeNodeResult): Promise<IAsyncHTMLElement> => {
|
||||
const selector: string = node.target[0];
|
||||
const elements: Array<IAsyncHTMLElement> = await context.querySelectorAll(selector);
|
||||
|
||||
return elements[0];
|
||||
};
|
||||
|
||||
/** Validate config to warn against overriden properties. */
|
||||
const validateConfig = (config: IChiselOptions) => {
|
||||
const warnings: Array<string> = [];
|
||||
|
||||
Object.keys(overrideLookUp).forEach((dominant: string) => {
|
||||
if (config.hasOwnProperty(dominant)) {
|
||||
const lessers = overrideLookUp[dominant].filter((lesserProperty) => {
|
||||
return config.hasOwnProperty(lesserProperty);
|
||||
});
|
||||
|
||||
if (lessers.length) {
|
||||
warnings.push(`${lessers.join(', ')} is ignored when ${dominant} is set.`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return warnings;
|
||||
};
|
||||
|
||||
const validate = async (traveseEnd: ITraverseEnd) => {
|
||||
const { resource } = traveseEnd;
|
||||
const chiselScript: string = await readFileAsync(require.resolve('@mskeros/chisel'));
|
||||
const runChiselScript: string = `(function() {
|
||||
${chiselScript};
|
||||
|
||||
return ${generateScript()}();
|
||||
}())`;
|
||||
let result: IChiselResults = null;
|
||||
|
||||
const warnings = validateConfig(chiselConfig);
|
||||
|
||||
if (warnings.length) {
|
||||
const reportPromises = warnings.map((warning) => {
|
||||
return context.report(resource, null, warning, null, null, Severity.warning);
|
||||
});
|
||||
|
||||
await Promise.all(reportPromises);
|
||||
}
|
||||
|
||||
try {
|
||||
result = await context.evaluate(runChiselScript);
|
||||
} catch (error) {
|
||||
await context.report(resource, null, `Error executing script${error.message ? `: "${error.message}"` : ''}. Please try with another connector`, null, null, Severity.warning);
|
||||
debug('Error executing script %O', error);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result || !Array.isArray(result.violations)) {
|
||||
debug(`Unable to parse chisel results ${result}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.violations.length === 0) {
|
||||
debug('No accessibility issues found');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const reportPromises = result.violations.reduce((promises: Array<Promise<void>>, violation: IChiselViolation) => {
|
||||
const elementPromises = violation.nodes.map(async (node: AxeNodeResult) => {
|
||||
const element: IAsyncHTMLElement = await getElement(node);
|
||||
|
||||
return context.report(resource, element, violation.help);
|
||||
});
|
||||
|
||||
return promises.concat(elementPromises);
|
||||
}, []);
|
||||
|
||||
await Promise.all(reportPromises);
|
||||
};
|
||||
|
||||
loadRuleConfig();
|
||||
|
||||
return { 'traverse::end': validate };
|
||||
},
|
||||
meta: {
|
||||
docs: {
|
||||
category: Category.accessibility,
|
||||
description: `Use Chisel to assess the accessibility of a web site or web application.`
|
||||
},
|
||||
recommended: false,
|
||||
schema: [{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
dom: { type: ['string', 'array'] },
|
||||
enableBestPracticeRules: { type: 'boolean' },
|
||||
exclude: {
|
||||
items: { type: 'string' },
|
||||
type: 'array'
|
||||
},
|
||||
include: {
|
||||
items: { type: 'string' },
|
||||
type: 'array'
|
||||
},
|
||||
selector: { type: 'string' },
|
||||
testsToRun: {
|
||||
items: { type: 'string' },
|
||||
type: 'array'
|
||||
}
|
||||
}
|
||||
}],
|
||||
worksWithLocalFiles: false
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { chisel: rule };
|
|
@ -0,0 +1,63 @@
|
|||
import { generateHTMLPage } from 'sonarwhal/dist/tests/helpers/misc';
|
||||
import { getRuleName } from 'sonarwhal/dist/src/lib/utils/rule-helpers';
|
||||
import { IRuleTest } from 'sonarwhal/dist/tests/helpers/rule-test-type';
|
||||
import * as ruleRunner from 'sonarwhal/dist/tests/helpers/rule-runner';
|
||||
|
||||
const ruleName = getRuleName(__dirname);
|
||||
|
||||
const html = {
|
||||
missingLang: `<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>test</title>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>`,
|
||||
noProblems: generateHTMLPage()
|
||||
};
|
||||
|
||||
const tests: Array<IRuleTest> = [
|
||||
{
|
||||
name: `Page doesn't have any a11y problems and passes`,
|
||||
serverConfig: html.noProblems
|
||||
},
|
||||
{
|
||||
name: `HTML is missing the lang attribute and fails`,
|
||||
reports: [{ message: '<html> element must have a lang attribute' }],
|
||||
serverConfig: html.missingLang
|
||||
}
|
||||
];
|
||||
|
||||
const testsWithCustomConfiguration: Array<IRuleTest> = [
|
||||
{
|
||||
name: `Page doesn't have any a11y problems and passes`,
|
||||
serverConfig: html.noProblems
|
||||
},
|
||||
{
|
||||
name: `HTML is missing the lang attribute and passes because of custom config`,
|
||||
serverConfig: html.missingLang
|
||||
}
|
||||
];
|
||||
|
||||
const testsWithOverridenConfiguration: Array<IRuleTest> = [
|
||||
{
|
||||
name: `Page doesn't have any a11y problems and passes, but warns about the overriden config setting`,
|
||||
reports: [{ message: 'exclude is ignored when selector is set.' }],
|
||||
serverConfig: html.noProblems
|
||||
},
|
||||
{
|
||||
name: `HTML is missing the lang attribute and fails, and warns about the overriden config setting`,
|
||||
reports: [{ message: 'exclude is ignored when selector is set.' }, { message: '<html> element must have a lang attribute' }],
|
||||
serverConfig: html.missingLang
|
||||
}
|
||||
];
|
||||
|
||||
ruleRunner.testRule(ruleName, tests);
|
||||
ruleRunner.testRule(ruleName, testsWithCustomConfiguration, { ruleOptions: { testsToRun: ['color-contrast'] } });
|
||||
ruleRunner.testRule(ruleName, testsWithOverridenConfiguration, {
|
||||
ruleOptions: {
|
||||
exclude: ['html'],
|
||||
selector: 'html'
|
||||
}
|
||||
});
|
Загрузка…
Ссылка в новой задаче