diff --git a/__tests__/__util__/helpers/getESLintCoreRule.js b/__tests__/__util__/helpers/getESLintCoreRule.js new file mode 100644 index 000000000..ee04494f0 --- /dev/null +++ b/__tests__/__util__/helpers/getESLintCoreRule.js @@ -0,0 +1,9 @@ +import { version } from 'eslint/package.json'; +import semver from 'semver'; + +const isESLintV8 = semver.major(version) >= 8; + +// eslint-disable-next-line global-require, import/no-dynamic-require, import/no-unresolved +const getESLintCoreRule = (ruleId) => (isESLintV8 ? require('eslint/use-at-your-own-risk').builtinRules.get(ruleId) : require(`eslint/lib/rules/${ruleId}`)); + +export default getESLintCoreRule; diff --git a/__tests__/__util__/helpers/parsers.js b/__tests__/__util__/helpers/parsers.js new file mode 100644 index 000000000..fc75cf337 --- /dev/null +++ b/__tests__/__util__/helpers/parsers.js @@ -0,0 +1,186 @@ +import path from 'path'; +import semver from 'semver'; +import entries from 'object.entries'; +import { version } from 'eslint/package.json'; +import flatMap from 'array.prototype.flatmap'; + +let tsParserVersion; +try { + // eslint-disable-next-line import/no-unresolved, global-require + tsParserVersion = require('@typescript-eslint/parser/package.json').version; +} catch (e) { /**/ } + +const disableNewTS = semver.satisfies(tsParserVersion, '>= 4.1') // this rule is not useful on v4.1+ of the TS parser + ? (x) => ({ ...x, features: [].concat(x.features, 'no-ts-new') }) + : (x) => x; + +function minEcmaVersion(features, parserOptions) { + const minEcmaVersionForFeatures = { + 'class fields': 2022, + 'optional chaining': 2020, + 'nullish coalescing': 2020, + }; + const result = Math.max( + ...[].concat( + (parserOptions && parserOptions.ecmaVersion) || [], + flatMap(entries(minEcmaVersionForFeatures), (entry) => { + const f = entry[0]; + const y = entry[1]; + return features.has(f) ? y : []; + }), + ).map((y) => (y > 5 && y < 2015 ? y + 2009 : y)), // normalize editions to years + ); + return Number.isFinite(result) ? result : undefined; +} + +const NODE_MODULES = '../../node_modules'; + +const parsers = { + BABEL_ESLINT: path.join(__dirname, NODE_MODULES, 'babel-eslint'), + '@BABEL_ESLINT': path.join(__dirname, NODE_MODULES, '@babel/eslint-parser'), + TYPESCRIPT_ESLINT: path.join(__dirname, NODE_MODULES, 'typescript-eslint-parser'), + '@TYPESCRIPT_ESLINT': path.join(__dirname, NODE_MODULES, '@typescript-eslint/parser'), + disableNewTS, + babelParserOptions: function parserOptions(test, features) { + return { + ...test.parserOptions, + requireConfigFile: false, + babelOptions: { + presets: [ + '@babel/preset-react', + ], + plugins: [ + '@babel/plugin-syntax-do-expressions', + '@babel/plugin-syntax-function-bind', + ['@babel/plugin-syntax-decorators', { legacy: true }], + ], + parserOpts: { + allowSuperOutsideMethod: false, + allowReturnOutsideFunction: false, + }, + }, + ecmaFeatures: { + + ...test.parserOptions && test.parserOptions.ecmaFeatures, + jsx: true, + modules: true, + legacyDecorators: features.has('decorators'), + }, + }; + }, + all: function all(tests) { + const t = flatMap(tests, (test) => { + /* eslint no-param-reassign: 0 */ + if (typeof test === 'string') { + test = { code: test }; + } + if ('parser' in test) { + delete test.features; + return test; + } + const features = new Set([].concat(test.features || [])); + delete test.features; + + const es = minEcmaVersion(features, test.parserOptions); + + function addComment(testObject, parser) { + const extras = [].concat( + `features: [${Array.from(features).join(',')}]`, + `parser: ${parser}`, + testObject.parserOptions ? `parserOptions: ${JSON.stringify(testObject.parserOptions)}` : [], + testObject.options ? `options: ${JSON.stringify(testObject.options)}` : [], + testObject.settings ? `settings: ${JSON.stringify(testObject.settings)}` : [], + ); + + const extraComment = `\n// ${extras.join(', ')}`; + + // Augment expected fix code output with extraComment + const nextCode = { code: testObject.code + extraComment }; + const nextOutput = testObject.output && { output: testObject.output + extraComment }; + + // Augment expected suggestion outputs with extraComment + // `errors` may be a number (expected number of errors) or an array of + // error objects. + const nextErrors = testObject.errors + && typeof testObject.errors !== 'number' + && { + errors: testObject.errors.map( + (errorObject) => { + const nextSuggestions = errorObject.suggestions && { + suggestions: errorObject.suggestions.map((suggestion) => ({ ...suggestion, output: suggestion.output + extraComment })), + }; + + return { ...errorObject, ...nextSuggestions }; + }, + ), + }; + + return { + + ...testObject, + ...nextCode, + ...nextOutput, + ...nextErrors, + }; + } + + const skipBase = (features.has('class fields') && semver.satisfies(version, '< 8')) + || (es >= 2020 && semver.satisfies(version, '< 6')) + || features.has('no-default') + || features.has('bind operator') + || features.has('do expressions') + || features.has('decorators') + || features.has('flow') + || features.has('ts') + || features.has('types') + || (features.has('fragment') && semver.satisfies(version, '< 5')); + + const skipBabel = features.has('no-babel'); + const skipOldBabel = skipBabel + || features.has('no-babel-old') + || features.has('optional chaining') + || semver.satisfies(version, '>= 8'); + const skipNewBabel = skipBabel + || features.has('no-babel-new') + || !semver.satisfies(version, '^7.5.0') // require('@babel/eslint-parser/package.json').peerDependencies.eslint + || features.has('flow') + || features.has('types') + || features.has('ts'); + const skipTS = semver.satisfies(version, '<= 5') // TODO: make these pass on eslint 5 + || features.has('no-ts') + || features.has('flow') + || features.has('jsx namespace') + || features.has('bind operator') + || features.has('do expressions'); + const tsOld = !skipTS && !features.has('no-ts-old'); + const tsNew = !skipTS && !features.has('no-ts-new'); + + return [].concat( + skipBase ? [] : addComment( + { + ...test, + ...typeof es === 'number' && { + parserOptions: { ...test.parserOptions, ecmaVersion: es }, + }, + }, + 'default', + ), + skipOldBabel ? [] : addComment({ + ...test, + parser: parsers.BABEL_ESLINT, + parserOptions: parsers.babelParserOptions(test, features), + }, 'babel-eslint'), + skipNewBabel ? [] : addComment({ + ...test, + parser: parsers['@BABEL_ESLINT'], + parserOptions: parsers.babelParserOptions(test, features), + }, '@babel/eslint-parser'), + tsOld ? addComment({ ...test, parser: parsers.TYPESCRIPT_ESLINT }, 'typescript-eslint') : [], + tsNew ? addComment({ ...test, parser: parsers['@TYPESCRIPT_ESLINT'] }, '@typescript-eslint/parser') : [], + ); + }); + return t; + }, +}; + +export default parsers; diff --git a/__tests__/src/rules/accessible-emoji-test.js b/__tests__/src/rules/accessible-emoji-test.js index dc3d19683..a1d21f461 100644 --- a/__tests__/src/rules/accessible-emoji-test.js +++ b/__tests__/src/rules/accessible-emoji-test.js @@ -10,6 +10,7 @@ import { RuleTester } from 'eslint'; import parserOptionsMapper from '../../__util__/parserOptionsMapper'; import rule from '../../../src/rules/accessible-emoji'; +import parsers from '../../__util__/helpers/parsers'; // ----------------------------------------------------------------------------- // Tests @@ -23,7 +24,7 @@ const expectedError = { }; ruleTester.run('accessible-emoji', rule, { - valid: [ + valid: parsers.all([].concat( { code: '
;' }, { code: '' }, { code: 'No emoji here!' }, @@ -42,8 +43,8 @@ ruleTester.run('accessible-emoji', rule, { code: '๐Ÿผ', settings: { 'jsx-a11y': { components: { CustomInput: 'input' } } }, }, - ].map(parserOptionsMapper), - invalid: [ + )).map(parserOptionsMapper), + invalid: parsers.all([].concat( { code: '๐Ÿผ', errors: [expectedError] }, { code: 'foo๐Ÿผbar', errors: [expectedError] }, { code: 'foo ๐Ÿผ bar', errors: [expectedError] }, @@ -52,5 +53,5 @@ ruleTester.run('accessible-emoji', rule, { { code: '๐Ÿผ', errors: [expectedError] }, { code: '๐Ÿผ', errors: [expectedError] }, { code: '๐Ÿผ', errors: [expectedError] }, - ].map(parserOptionsMapper), + )).map(parserOptionsMapper), }); diff --git a/__tests__/src/rules/alt-text-test.js b/__tests__/src/rules/alt-text-test.js index bd2463d73..07b2c8259 100644 --- a/__tests__/src/rules/alt-text-test.js +++ b/__tests__/src/rules/alt-text-test.js @@ -9,6 +9,7 @@ import { RuleTester } from 'eslint'; import parserOptionsMapper from '../../__util__/parserOptionsMapper'; +import parsers from '../../__util__/helpers/parsers'; import rule from '../../../src/rules/alt-text'; // ----------------------------------------------------------------------------- @@ -28,19 +29,29 @@ Use alt="" for presentational images.`, type: 'JSXOpeningElement', }); -const ariaLabelValueError = 'The aria-label attribute must have a value. The alt attribute is preferred over aria-label for images.'; -const ariaLabelledbyValueError = 'The aria-labelledby attribute must have a value. The alt attribute is preferred over aria-labelledby for images.'; +const ariaLabelValueError = { + message: 'The aria-label attribute must have a value. The alt attribute is preferred over aria-label for images.', +}; +const ariaLabelledbyValueError = { + message: 'The aria-labelledby attribute must have a value. The alt attribute is preferred over aria-labelledby for images.', +}; const preferAltError = () => ({ message: 'Prefer alt="" over a presentational role. First rule of aria is to not use aria if it can be achieved via native HTML.', type: 'JSXOpeningElement', }); -const objectError = 'Embedded elements must have alternative text by providing inner text, aria-label or aria-labelledby props.'; +const objectError = { + message: 'Embedded elements must have alternative text by providing inner text, aria-label or aria-labelledby props.', +}; -const areaError = 'Each area of an image map must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` prop.'; +const areaError = { + message: 'Each area of an image map must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` prop.', +}; -const inputImageError = ' elements with type="image" must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` prop.'; +const inputImageError = { + message: ' elements with type="image" must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` prop.', +}; const componentsSettings = { 'jsx-a11y': { @@ -58,7 +69,7 @@ const array = [{ }]; ruleTester.run('alt-text', rule, { - valid: [ + valid: parsers.all([].concat( // DEFAULT ELEMENT 'img' TESTS { code: 'foo;' }, { code: '{"foo"};' }, @@ -166,8 +177,8 @@ ruleTester.run('alt-text', rule, { { code: '', options: array }, { code: '', options: array }, { code: '', options: array }, - ].map(parserOptionsMapper), - invalid: [ + )).map(parserOptionsMapper), + invalid: parsers.all([].concat( // DEFAULT ELEMENT 'img' TESTS { code: ';', errors: [missingPropError('img')] }, { code: ';', errors: [altValueError('img')] }, @@ -273,5 +284,5 @@ ruleTester.run('alt-text', rule, { { code: 'Foo', errors: [inputImageError], options: array }, { code: '', errors: [inputImageError], options: array }, { code: '', errors: [inputImageError], settings: componentsSettings }, - ].map(parserOptionsMapper), + )).map(parserOptionsMapper), }); diff --git a/__tests__/src/rules/anchor-ambiguous-text-test.js b/__tests__/src/rules/anchor-ambiguous-text-test.js index da57274cd..cbbc39501 100644 --- a/__tests__/src/rules/anchor-ambiguous-text-test.js +++ b/__tests__/src/rules/anchor-ambiguous-text-test.js @@ -10,6 +10,7 @@ import { RuleTester } from 'eslint'; import parserOptionsMapper from '../../__util__/parserOptionsMapper'; +import parsers from '../../__util__/helpers/parsers'; import rule from '../../../src/rules/anchor-ambiguous-text'; // ----------------------------------------------------------------------------- @@ -34,7 +35,7 @@ const expectedErrorGenerator = (words) => ({ const expectedError = expectedErrorGenerator(DEFAULT_AMBIGUOUS_WORDS); ruleTester.run('anchor-ambiguous-text', rule, { - valid: [ + valid: parsers.all([].concat( { code: 'documentation;' }, { code: '${here};' }, { code: 'click here;' }, @@ -69,8 +70,8 @@ ruleTester.run('anchor-ambiguous-text', rule, { }], settings: { 'jsx-a11y': { components: { Link: 'a' } } }, }, - ].map(parserOptionsMapper), - invalid: [ + )).map(parserOptionsMapper), + invalid: parsers.all([].concat( { code: 'here;', errors: [expectedError] }, { code: 'HERE;', errors: [expectedError] }, { code: 'click here;', errors: [expectedError] }, @@ -113,5 +114,5 @@ ruleTester.run('anchor-ambiguous-text', rule, { words: ['a disallowed word'], }], }, - ].map(parserOptionsMapper), + )).map(parserOptionsMapper), }); diff --git a/__tests__/src/rules/anchor-has-content-test.js b/__tests__/src/rules/anchor-has-content-test.js index eb47e8e55..c55dbb9fb 100644 --- a/__tests__/src/rules/anchor-has-content-test.js +++ b/__tests__/src/rules/anchor-has-content-test.js @@ -9,6 +9,7 @@ import { RuleTester } from 'eslint'; import parserOptionsMapper from '../../__util__/parserOptionsMapper'; +import parsers from '../../__util__/helpers/parsers'; import rule from '../../../src/rules/anchor-has-content'; // ----------------------------------------------------------------------------- @@ -23,7 +24,7 @@ const expectedError = { }; ruleTester.run('anchor-has-content', rule, { - valid: [ + valid: parsers.all([].concat( { code: '
;' }, { code: 'Foo' }, { code: '' }, @@ -39,8 +40,8 @@ ruleTester.run('anchor-has-content', rule, { { code: '' }, { code: '' }, { code: '' }, - ].map(parserOptionsMapper), - invalid: [ + )).map(parserOptionsMapper), + invalid: parsers.all([].concat( { code: '', errors: [expectedError] }, { code: '', errors: [expectedError] }, { code: '{undefined}', errors: [expectedError] }, @@ -49,5 +50,5 @@ ruleTester.run('anchor-has-content', rule, { errors: [expectedError], settings: { 'jsx-a11y': { components: { Link: 'a' } } }, }, - ].map(parserOptionsMapper), + )).map(parserOptionsMapper), }); diff --git a/__tests__/src/rules/anchor-is-valid-test.js b/__tests__/src/rules/anchor-is-valid-test.js index 5b368d9e7..9fb4025e9 100644 --- a/__tests__/src/rules/anchor-is-valid-test.js +++ b/__tests__/src/rules/anchor-is-valid-test.js @@ -9,6 +9,7 @@ import { RuleTester } from 'eslint'; import parserOptionsMapper from '../../__util__/parserOptionsMapper'; +import parsers from '../../__util__/helpers/parsers'; import rule from '../../../src/rules/anchor-is-valid'; // ----------------------------------------------------------------------------- @@ -88,7 +89,7 @@ const componentsSettings = { }; ruleTester.run('anchor-is-valid', rule, { - valid: [ + valid: parsers.all([].concat( // DEFAULT ELEMENT 'a' TESTS { code: '' }, { code: '' }, @@ -279,8 +280,8 @@ ruleTester.run('anchor-is-valid', rule, { { code: '', options: componentsAndSpecialLinkAndInvalidHrefAspect }, { code: '', options: componentsAndSpecialLinkAndInvalidHrefAspect }, - ].map(parserOptionsMapper), - invalid: [ + )).map(parserOptionsMapper), + invalid: parsers.all([].concat( // DEFAULT ELEMENT 'a' TESTS // NO HREF { code: '', errors: [noHrefexpectedError] }, @@ -288,8 +289,8 @@ ruleTester.run('anchor-is-valid', rule, { { code: '', errors: [noHrefexpectedError] }, // INVALID HREF { code: ';', errors: [invalidHrefexpectedError] }, - { code: '', errors: [invalidHrefErrorMessage] }, - { code: '', errors: [invalidHrefErrorMessage] }, + { code: '', errors: [invalidHrefexpectedError] }, + { code: '', errors: [invalidHrefexpectedError] }, { code: '', errors: [invalidHrefexpectedError] }, { code: '', errors: [invalidHrefexpectedError] }, // SHOULD BE BUTTON @@ -308,13 +309,13 @@ ruleTester.run('anchor-is-valid', rule, { { code: '', errors: [noHrefexpectedError], options: components }, // INVALID HREF { code: '', errors: [invalidHrefexpectedError], options: components }, - { code: '', errors: [invalidHrefErrorMessage], options: components }, - { code: '', errors: [invalidHrefErrorMessage], options: components }, + { code: '', errors: [invalidHrefexpectedError], options: components }, + { code: '', errors: [invalidHrefexpectedError], options: components }, { code: '', errors: [invalidHrefexpectedError], options: components }, { code: '', errors: [invalidHrefexpectedError], options: components }, { code: '', errors: [invalidHrefexpectedError], options: components }, - { code: '', errors: [invalidHrefErrorMessage], options: components }, - { code: '', errors: [invalidHrefErrorMessage], options: components }, + { code: '', errors: [invalidHrefexpectedError], options: components }, + { code: '', errors: [invalidHrefexpectedError], options: components }, { code: '', errors: [invalidHrefexpectedError], options: components }, { code: '', errors: [invalidHrefexpectedError], options: components }, // SHOULD BE BUTTON @@ -354,8 +355,8 @@ ruleTester.run('anchor-is-valid', rule, { { code: '', errors: [noHrefexpectedError], options: specialLink }, // INVALID HREF { code: ';', errors: [invalidHrefexpectedError], options: specialLink }, - { code: '', errors: [invalidHrefErrorMessage], options: specialLink }, - { code: '', errors: [invalidHrefErrorMessage], options: specialLink }, + { code: '', errors: [invalidHrefexpectedError], options: specialLink }, + { code: '', errors: [invalidHrefexpectedError], options: specialLink }, { code: '', errors: [invalidHrefexpectedError], options: specialLink }, { code: '', errors: [invalidHrefexpectedError], options: specialLink }, // SHOULD BE BUTTON @@ -377,8 +378,8 @@ ruleTester.run('anchor-is-valid', rule, { { code: '', errors: [noHrefexpectedError], options: componentsAndSpecialLink }, // INVALID HREF { code: ';', errors: [invalidHrefexpectedError], options: componentsAndSpecialLink }, - { code: '', errors: [invalidHrefErrorMessage], options: componentsAndSpecialLink }, - { code: '', errors: [invalidHrefErrorMessage], options: componentsAndSpecialLink }, + { code: '', errors: [invalidHrefexpectedError], options: componentsAndSpecialLink }, + { code: '', errors: [invalidHrefexpectedError], options: componentsAndSpecialLink }, { code: '', errors: [invalidHrefexpectedError], @@ -408,149 +409,149 @@ ruleTester.run('anchor-is-valid', rule, { // WITH ASPECTS TESTS // NO HREF - { code: '', options: noHrefAspect, errors: [noHrefErrorMessage] }, - { code: '', options: noHrefPreferButtonAspect, errors: [noHrefErrorMessage] }, - { code: '', options: noHrefInvalidHrefAspect, errors: [noHrefErrorMessage] }, - { code: '', options: noHrefAspect, errors: [noHrefErrorMessage] }, - { code: '', options: noHrefPreferButtonAspect, errors: [noHrefErrorMessage] }, - { code: '', options: noHrefInvalidHrefAspect, errors: [noHrefErrorMessage] }, - { code: '', options: noHrefAspect, errors: [noHrefErrorMessage] }, - { code: '', options: noHrefPreferButtonAspect, errors: [noHrefErrorMessage] }, - { code: '', options: noHrefInvalidHrefAspect, errors: [noHrefErrorMessage] }, + { code: '', options: noHrefAspect, errors: [noHrefexpectedError] }, + { code: '', options: noHrefPreferButtonAspect, errors: [noHrefexpectedError] }, + { code: '', options: noHrefInvalidHrefAspect, errors: [noHrefexpectedError] }, + { code: '', options: noHrefAspect, errors: [noHrefexpectedError] }, + { code: '', options: noHrefPreferButtonAspect, errors: [noHrefexpectedError] }, + { code: '', options: noHrefInvalidHrefAspect, errors: [noHrefexpectedError] }, + { code: '', options: noHrefAspect, errors: [noHrefexpectedError] }, + { code: '', options: noHrefPreferButtonAspect, errors: [noHrefexpectedError] }, + { code: '', options: noHrefInvalidHrefAspect, errors: [noHrefexpectedError] }, // INVALID HREF - { code: ';', options: invalidHrefAspect, errors: [invalidHrefErrorMessage] }, - { code: ';', options: noHrefInvalidHrefAspect, errors: [invalidHrefErrorMessage] }, - { code: ';', options: preferButtonInvalidHrefAspect, errors: [invalidHrefErrorMessage] }, - { code: ';', options: invalidHrefAspect, errors: [invalidHrefErrorMessage] }, - { code: ';', options: noHrefInvalidHrefAspect, errors: [invalidHrefErrorMessage] }, - { code: ';', options: preferButtonInvalidHrefAspect, errors: [invalidHrefErrorMessage] }, - { code: ';', options: invalidHrefAspect, errors: [invalidHrefErrorMessage] }, - { code: ';', options: noHrefInvalidHrefAspect, errors: [invalidHrefErrorMessage] }, - { code: ';', options: preferButtonInvalidHrefAspect, errors: [invalidHrefErrorMessage] }, - { code: ';', options: invalidHrefAspect, errors: [invalidHrefErrorMessage] }, - { code: ';', options: noHrefInvalidHrefAspect, errors: [invalidHrefErrorMessage] }, + { code: ';', options: invalidHrefAspect, errors: [invalidHrefexpectedError] }, + { code: ';', options: noHrefInvalidHrefAspect, errors: [invalidHrefexpectedError] }, + { code: ';', options: preferButtonInvalidHrefAspect, errors: [invalidHrefexpectedError] }, + { code: ';', options: invalidHrefAspect, errors: [invalidHrefexpectedError] }, + { code: ';', options: noHrefInvalidHrefAspect, errors: [invalidHrefexpectedError] }, + { code: ';', options: preferButtonInvalidHrefAspect, errors: [invalidHrefexpectedError] }, + { code: ';', options: invalidHrefAspect, errors: [invalidHrefexpectedError] }, + { code: ';', options: noHrefInvalidHrefAspect, errors: [invalidHrefexpectedError] }, + { code: ';', options: preferButtonInvalidHrefAspect, errors: [invalidHrefexpectedError] }, + { code: ';', options: invalidHrefAspect, errors: [invalidHrefexpectedError] }, + { code: ';', options: noHrefInvalidHrefAspect, errors: [invalidHrefexpectedError] }, { code: ';', options: preferButtonInvalidHrefAspect, - errors: [invalidHrefErrorMessage], + errors: [invalidHrefexpectedError], }, - { code: ';', options: invalidHrefAspect, errors: [invalidHrefErrorMessage] }, - { code: ';', options: noHrefInvalidHrefAspect, errors: [invalidHrefErrorMessage] }, + { code: ';', options: invalidHrefAspect, errors: [invalidHrefexpectedError] }, + { code: ';', options: noHrefInvalidHrefAspect, errors: [invalidHrefexpectedError] }, { code: ';', options: preferButtonInvalidHrefAspect, - errors: [invalidHrefErrorMessage], + errors: [invalidHrefexpectedError], }, // SHOULD BE BUTTON - { code: ' void 0} />', options: preferButtonAspect, errors: [preferButtonErrorMessage] }, + { code: ' void 0} />', options: preferButtonAspect, errors: [preferButtonexpectedError] }, { code: ' void 0} />', options: preferButtonInvalidHrefAspect, - errors: [preferButtonErrorMessage], + errors: [preferButtonexpectedError], }, - { code: ' void 0} />', options: noHrefPreferButtonAspect, errors: [preferButtonErrorMessage] }, - { code: ' void 0} />', options: noHrefAspect, errors: [noHrefErrorMessage] }, - { code: ' void 0} />', options: noHrefInvalidHrefAspect, errors: [noHrefErrorMessage] }, - { code: ' void 0} />', options: preferButtonAspect, errors: [preferButtonErrorMessage] }, + { code: ' void 0} />', options: noHrefPreferButtonAspect, errors: [preferButtonexpectedError] }, + { code: ' void 0} />', options: noHrefAspect, errors: [noHrefexpectedError] }, + { code: ' void 0} />', options: noHrefInvalidHrefAspect, errors: [noHrefexpectedError] }, + { code: ' void 0} />', options: preferButtonAspect, errors: [preferButtonexpectedError] }, { code: ' void 0} />', options: noHrefPreferButtonAspect, - errors: [preferButtonErrorMessage], + errors: [preferButtonexpectedError], }, { code: ' void 0} />', options: preferButtonInvalidHrefAspect, - errors: [preferButtonErrorMessage], + errors: [preferButtonexpectedError], }, - { code: ' void 0} />', options: invalidHrefAspect, errors: [invalidHrefErrorMessage] }, + { code: ' void 0} />', options: invalidHrefAspect, errors: [invalidHrefexpectedError] }, { code: ' void 0} />', options: noHrefInvalidHrefAspect, - errors: [invalidHrefErrorMessage], + errors: [invalidHrefexpectedError], }, { code: ' void 0} />', options: preferButtonAspect, - errors: [preferButtonErrorMessage], + errors: [preferButtonexpectedError], }, { code: ' void 0} />', options: noHrefPreferButtonAspect, - errors: [preferButtonErrorMessage], + errors: [preferButtonexpectedError], }, { code: ' void 0} />', options: preferButtonInvalidHrefAspect, - errors: [preferButtonErrorMessage], + errors: [preferButtonexpectedError], }, { code: ' void 0} />', options: invalidHrefAspect, - errors: [invalidHrefErrorMessage], + errors: [invalidHrefexpectedError], }, { code: ' void 0} />', options: noHrefInvalidHrefAspect, - errors: [invalidHrefErrorMessage], + errors: [invalidHrefexpectedError], }, { code: ' void 0} />', options: preferButtonAspect, - errors: [preferButtonErrorMessage], + errors: [preferButtonexpectedError], }, { code: ' void 0} />', options: noHrefPreferButtonAspect, - errors: [preferButtonErrorMessage], + errors: [preferButtonexpectedError], }, { code: ' void 0} />', options: preferButtonInvalidHrefAspect, - errors: [preferButtonErrorMessage], + errors: [preferButtonexpectedError], }, { code: ' void 0} />', options: invalidHrefAspect, - errors: [invalidHrefErrorMessage], + errors: [invalidHrefexpectedError], }, { code: ' void 0} />', options: noHrefInvalidHrefAspect, - errors: [invalidHrefErrorMessage], + errors: [invalidHrefexpectedError], }, // CUSTOM COMPONENTS AND SPECIALLINK AND ASPECT { code: '', options: componentsAndSpecialLinkAndNoHrefAspect, - errors: [noHrefErrorMessage], + errors: [noHrefexpectedError], }, { code: '', options: componentsAndSpecialLinkAndNoHrefAspect, - errors: [noHrefErrorMessage], + errors: [noHrefexpectedError], }, { code: '', options: componentsAndSpecialLinkAndNoHrefAspect, - errors: [noHrefErrorMessage], + errors: [noHrefexpectedError], }, { code: '', options: componentsAndSpecialLinkAndNoHrefAspect, - errors: [noHrefErrorMessage], + errors: [noHrefexpectedError], }, { code: '', options: componentsAndSpecialLinkAndNoHrefAspect, - errors: [noHrefErrorMessage], + errors: [noHrefexpectedError], }, { code: '', options: componentsAndSpecialLinkAndNoHrefAspect, - errors: [noHrefErrorMessage], + errors: [noHrefexpectedError], }, - ].map(parserOptionsMapper), + )).map(parserOptionsMapper), }); diff --git a/__tests__/src/rules/aria-activedescendant-has-tabindex-test.js b/__tests__/src/rules/aria-activedescendant-has-tabindex-test.js index 6c251cf23..5310ac9ac 100644 --- a/__tests__/src/rules/aria-activedescendant-has-tabindex-test.js +++ b/__tests__/src/rules/aria-activedescendant-has-tabindex-test.js @@ -9,6 +9,7 @@ import { RuleTester } from 'eslint'; import parserOptionsMapper from '../../__util__/parserOptionsMapper'; +import parsers from '../../__util__/helpers/parsers'; import rule from '../../../src/rules/aria-activedescendant-has-tabindex'; // ----------------------------------------------------------------------------- @@ -23,7 +24,7 @@ const expectedError = { }; ruleTester.run('aria-activedescendant-has-tabindex', rule, { - valid: [ + valid: parsers.all([].concat( { code: ';', }, @@ -79,8 +80,8 @@ ruleTester.run('aria-activedescendant-has-tabindex', rule, { { code: ';', }, - ].map(parserOptionsMapper), - invalid: [ + )).map(parserOptionsMapper), + invalid: parsers.all([].concat( { code: '
;', errors: [expectedError], @@ -90,5 +91,5 @@ ruleTester.run('aria-activedescendant-has-tabindex', rule, { errors: [expectedError], settings: { 'jsx-a11y': { components: { CustomComponent: 'div' } } }, }, - ].map(parserOptionsMapper), + )).map(parserOptionsMapper), }); diff --git a/__tests__/src/rules/aria-props-test.js b/__tests__/src/rules/aria-props-test.js index 89aa9864d..1236fa7c7 100644 --- a/__tests__/src/rules/aria-props-test.js +++ b/__tests__/src/rules/aria-props-test.js @@ -10,6 +10,7 @@ import { aria } from 'aria-query'; import { RuleTester } from 'eslint'; import parserOptionsMapper from '../../__util__/parserOptionsMapper'; +import parsers from '../../__util__/helpers/parsers'; import rule from '../../../src/rules/aria-props'; import getSuggestion from '../../../src/util/getSuggestion'; @@ -43,7 +44,7 @@ const basicValidityTests = ariaAttributes.map((prop) => ({ })); ruleTester.run('aria-props', rule, { - valid: [ + valid: parsers.all([].concat( // Variables should pass, as we are only testing literals. { code: '
' }, { code: '
' }, @@ -53,8 +54,8 @@ ruleTester.run('aria-props', rule, { { code: '
' }, { code: '' }, { code: '' }, - ].concat(basicValidityTests).map(parserOptionsMapper), - invalid: [ + )).concat(basicValidityTests).map(parserOptionsMapper), + invalid: parsers.all([].concat( { code: '
', errors: [errorMessage('aria-')] }, { code: '
', @@ -64,5 +65,5 @@ ruleTester.run('aria-props', rule, { code: '
', errors: [errorMessage('aria-skldjfaria-klajsd')], }, - ].map(parserOptionsMapper), + )).map(parserOptionsMapper), }); diff --git a/__tests__/src/rules/aria-proptypes-test.js b/__tests__/src/rules/aria-proptypes-test.js index f9ff1c8f7..67be3304a 100644 --- a/__tests__/src/rules/aria-proptypes-test.js +++ b/__tests__/src/rules/aria-proptypes-test.js @@ -11,6 +11,7 @@ import { aria } from 'aria-query'; import { RuleTester } from 'eslint'; import expect from 'expect'; import parserOptionsMapper from '../../__util__/parserOptionsMapper'; +import parsers from '../../__util__/helpers/parsers'; import rule from '../../../src/rules/aria-proptypes'; const { validityCheck } = rule; @@ -29,22 +30,24 @@ const errorMessage = (name) => { switch (type) { case 'tristate': - return `The value for ${name} must be a boolean or the string "mixed".`; + return { message: `The value for ${name} must be a boolean or the string "mixed".` }; case 'token': - return `The value for ${name} must be a single token from the following: ${permittedValues}.`; + return { message: `The value for ${name} must be a single token from the following: ${permittedValues}.` }; case 'tokenlist': - return `The value for ${name} must be a list of one or more \ -tokens from the following: ${permittedValues}.`; + return { + message: `The value for ${name} must be a list of one or more \ +tokens from the following: ${permittedValues}.`, + }; case 'idlist': - return `The value for ${name} must be a list of strings that represent DOM element IDs (idlist)`; + return { message: `The value for ${name} must be a list of strings that represent DOM element IDs (idlist)` }; case 'id': - return `The value for ${name} must be a string that represents a DOM element ID`; + return { message: `The value for ${name} must be a string that represents a DOM element ID` }; case 'boolean': case 'string': case 'integer': case 'number': default: - return `The value for ${name} must be a ${type}.`; + return { message: `The value for ${name} must be a ${type}.` }; } }; @@ -58,7 +61,7 @@ describe('validityCheck', () => { }); ruleTester.run('aria-proptypes', rule, { - valid: [ + valid: parsers.all([].concat( // DON'T TEST INVALID ARIA-* PROPS { code: '
' }, { code: '
' }, @@ -211,8 +214,8 @@ ruleTester.run('aria-proptypes', rule, { { code: '
' }, { code: '
' }, { code: '
' }, - ].map(parserOptionsMapper), - invalid: [ + )).map(parserOptionsMapper), + invalid: parsers.all([].concat( // BOOLEAN { code: '
', errors: [errorMessage('aria-hidden')] }, { code: '
', errors: [errorMessage('aria-hidden')] }, @@ -302,5 +305,5 @@ ruleTester.run('aria-proptypes', rule, { code: '
', errors: [errorMessage('aria-relevant')], }, - ].map(parserOptionsMapper), + )).map(parserOptionsMapper), }); diff --git a/__tests__/src/rules/aria-role-test.js b/__tests__/src/rules/aria-role-test.js index cf1ffccf9..410749666 100644 --- a/__tests__/src/rules/aria-role-test.js +++ b/__tests__/src/rules/aria-role-test.js @@ -10,6 +10,7 @@ import { roles } from 'aria-query'; import { RuleTester } from 'eslint'; import parserOptionsMapper from '../../__util__/parserOptionsMapper'; +import parsers from '../../__util__/helpers/parsers'; import rule from '../../../src/rules/aria-role'; // ----------------------------------------------------------------------------- @@ -56,7 +57,7 @@ const customDivSettings = { }; ruleTester.run('aria-role', rule, { - valid: [ + valid: parsers.all([].concat( // Variables should pass, as we are only testing literals. { code: '
' }, { code: '
' }, @@ -81,9 +82,9 @@ ruleTester.run('aria-role', rule, { { code: '', }, - ].concat(validTests).map(parserOptionsMapper), + )).concat(validTests).map(parserOptionsMapper), - invalid: [ + invalid: parsers.all([].concat( { code: '
', errors: [errorMessage] }, { code: '
', errors: [errorMessage] }, { code: '
', errors: [errorMessage] }, @@ -104,5 +105,5 @@ ruleTester.run('aria-role', rule, { options: ignoreNonDOMSchema, settings: customDivSettings, }, - ].concat(invalidTests).map(parserOptionsMapper), + )).concat(invalidTests).map(parserOptionsMapper), }); diff --git a/__tests__/src/rules/aria-unsupported-elements-test.js b/__tests__/src/rules/aria-unsupported-elements-test.js index 1e1b94212..31b348841 100644 --- a/__tests__/src/rules/aria-unsupported-elements-test.js +++ b/__tests__/src/rules/aria-unsupported-elements-test.js @@ -11,6 +11,7 @@ import { dom } from 'aria-query'; import { RuleTester } from 'eslint'; import parserOptionsMapper from '../../__util__/parserOptionsMapper'; +import parsers from '../../__util__/helpers/parsers'; import rule from '../../../src/rules/aria-unsupported-elements'; // ----------------------------------------------------------------------------- @@ -68,7 +69,7 @@ const invalidAriaValidityTests = domElements })); ruleTester.run('aria-unsupported-elements', rule, { - valid: roleValidityTests.concat(ariaValidityTests).map(parserOptionsMapper), - invalid: invalidRoleValidityTests.concat(invalidAriaValidityTests) + valid: parsers.all([].concat(roleValidityTests.concat(ariaValidityTests))).map(parserOptionsMapper), + invalid: parsers.all([].concat(invalidRoleValidityTests.concat(invalidAriaValidityTests))) .map(parserOptionsMapper), }); diff --git a/__tests__/src/rules/autocomplete-valid-test.js b/__tests__/src/rules/autocomplete-valid-test.js index 49f194067..57b1257c0 100644 --- a/__tests__/src/rules/autocomplete-valid-test.js +++ b/__tests__/src/rules/autocomplete-valid-test.js @@ -10,6 +10,7 @@ import { RuleTester } from 'eslint'; import parserOptionsMapper from '../../__util__/parserOptionsMapper'; import { axeFailMessage } from '../../__util__/axeMapping'; +import parsers from '../../__util__/helpers/parsers'; import rule from '../../../src/rules/autocomplete-valid'; // ----------------------------------------------------------------------------- @@ -37,7 +38,7 @@ const componentsSettings = { }; ruleTester.run('autocomplete-valid', rule, { - valid: [ + valid: parsers.all([].concat( // INAPPLICABLE { code: ';' }, // // PASSED AUTOCOMPLETE @@ -63,8 +64,8 @@ ruleTester.run('autocomplete-valid', rule, { { code: ';', errors: inappropriateAutocomplete }, { code: ';', errors: inappropriateAutocomplete }, { code: ';', errors: inappropriateAutocomplete, options: [{ inputComponents: ['Foo'] }] }, - ].map(parserOptionsMapper), - invalid: [ + )).map(parserOptionsMapper), + invalid: parsers.all([].concat( // FAILED "autocomplete-valid" { code: ';', errors: invalidAutocomplete }, { code: ';', errors: invalidAutocomplete }, @@ -72,5 +73,5 @@ ruleTester.run('autocomplete-valid', rule, { { code: ';', errors: invalidAutocomplete }, { code: ';', errors: invalidAutocomplete, options: [{ inputComponents: ['Bar'] }] }, { code: '', errors: invalidAutocomplete, settings: componentsSettings }, - ].map(parserOptionsMapper), + )).map(parserOptionsMapper), }); diff --git a/__tests__/src/rules/click-events-have-key-events-test.js b/__tests__/src/rules/click-events-have-key-events-test.js index 5460504f6..683a71b7b 100644 --- a/__tests__/src/rules/click-events-have-key-events-test.js +++ b/__tests__/src/rules/click-events-have-key-events-test.js @@ -9,6 +9,7 @@ import { RuleTester } from 'eslint'; import parserOptionsMapper from '../../__util__/parserOptionsMapper'; +import parsers from '../../__util__/helpers/parsers'; import rule from '../../../src/rules/click-events-have-key-events'; // ----------------------------------------------------------------------------- @@ -25,7 +26,7 @@ const expectedError = { }; ruleTester.run('click-events-have-key-events', rule, { - valid: [ + valid: parsers.all([].concat( { code: '
void 0} onKeyDown={foo}/>;' }, { code: '
void 0} onKeyUp={foo} />;' }, { code: '
void 0} onKeyPress={foo}/>;' }, @@ -52,8 +53,8 @@ ruleTester.run('click-events-have-key-events', rule, { { code: '' }, { code: '