diff --git a/.flowconfig b/.flowconfig index c39ebbab2..11145d7b2 100644 --- a/.flowconfig +++ b/.flowconfig @@ -2,3 +2,5 @@ /lib/.* /docs/.* /reports/.* +[options] +suppress_type=$FlowFixMe diff --git a/__mocks__/IdentifierMock.js b/__mocks__/IdentifierMock.js index 2b51c7271..971767330 100644 --- a/__mocks__/IdentifierMock.js +++ b/__mocks__/IdentifierMock.js @@ -1,4 +1,13 @@ -export default function IdentifierMock(ident) { +/** + * @flow + */ + +export type IdentifierMockType = {| + type: 'Identifier', + name: string, +|}; + +export default function IdentifierMock(ident: string): IdentifierMockType { return { type: 'Identifier', name: ident, diff --git a/__mocks__/JSXAttributeMock.js b/__mocks__/JSXAttributeMock.js index 50fc4f20c..b9410e7e1 100644 --- a/__mocks__/JSXAttributeMock.js +++ b/__mocks__/JSXAttributeMock.js @@ -1,7 +1,20 @@ +/** + * @flow + */ + import toAST from 'to-ast'; // eslint-disable-line import/no-extraneous-dependencies import JSXExpressionContainerMock from './JSXExpressionContainerMock'; -export default function JSXAttributeMock(prop, value, isExpressionContainer = false) { +export type JSXAttributeMockType = { + type: 'JSXAttribute', + name: { + type: 'JSXIdentifier', + name: string, + }, + value: mixed, +}; + +export default function JSXAttributeMock(prop: string, value: mixed, isExpressionContainer?: boolean = false): JSXAttributeMockType { let astValue; if (value && value.type !== undefined) { astValue = value; diff --git a/__mocks__/JSXElementMock.js b/__mocks__/JSXElementMock.js index 1cc05af70..a79da8505 100644 --- a/__mocks__/JSXElementMock.js +++ b/__mocks__/JSXElementMock.js @@ -2,9 +2,9 @@ * @flow */ -import JSXAttributeMock from './JSXAttributeMock'; +import type { JSXAttributeMockType } from './JSXAttributeMock'; -export type TJSXElementMock = { +export type JSXElementMockType = { type: 'JSXElement', openingElement: { type: 'JSXOpeningElement', @@ -12,16 +12,16 @@ export type TJSXElementMock = { type: 'JSXIdentifier', name: string, }, - attributes: Array, + attributes: Array, }, children: Array, }; export default function JSXElementMock( tagName: string, - attributes: Array = [], - children: Array = [], -): TJSXElementMock { + attributes: Array = [], + children?: Array = [], +): JSXElementMockType { return { type: 'JSXElement', openingElement: { diff --git a/__mocks__/JSXExpressionContainerMock.js b/__mocks__/JSXExpressionContainerMock.js index 7f15af5bb..6f2bd70bb 100644 --- a/__mocks__/JSXExpressionContainerMock.js +++ b/__mocks__/JSXExpressionContainerMock.js @@ -1,4 +1,13 @@ -export default function JSXExpressionContainerMock(exp) { +/** + * @flow + */ + +export type JSXExpressionContainerMockType = { + type: 'JSXExpressionContainer', + expression: mixed, +} + +export default function JSXExpressionContainerMock(exp: mixed): JSXExpressionContainerMockType { return { type: 'JSXExpressionContainer', expression: exp, diff --git a/__mocks__/JSXSpreadAttributeMock.js b/__mocks__/JSXSpreadAttributeMock.js index a11cc4117..80ef313f5 100644 --- a/__mocks__/JSXSpreadAttributeMock.js +++ b/__mocks__/JSXSpreadAttributeMock.js @@ -3,8 +3,14 @@ */ import IdentifierMock from './IdentifierMock'; +import type { IdentifierMockType } from './IdentifierMock'; -export default function JSXSpreadAttributeMock(identifier: string) { +export type JSXSpreadAttributeMockType = { + type: 'JSXSpreadAttribute', + argument: IdentifierMockType, +}; + +export default function JSXSpreadAttributeMock(identifier: string): JSXSpreadAttributeMockType { return { type: 'JSXSpreadAttribute', argument: IdentifierMock(identifier), diff --git a/__mocks__/JSXTextMock.js b/__mocks__/JSXTextMock.js index f94a12b55..bb4db2e0b 100644 --- a/__mocks__/JSXTextMock.js +++ b/__mocks__/JSXTextMock.js @@ -1,7 +1,14 @@ /** * @flow */ -export default function JSXTextMock(value: string) { + +export type JSXTextMockType = {| + type: 'JSXText', + value: string, + raw: string, +|}; + +export default function JSXTextMock(value: string): JSXTextMockType { return { type: 'JSXText', value, diff --git a/__mocks__/LiteralMock.js b/__mocks__/LiteralMock.js index 24f885c50..529d15b0e 100644 --- a/__mocks__/LiteralMock.js +++ b/__mocks__/LiteralMock.js @@ -1,7 +1,14 @@ /** * @flow */ -export default function LiteralMock(value: string) { + +export type LiteralMockType = {| + type: 'Literal', + value: string, + raw: string, +|}; + +export default function LiteralMock(value: string): LiteralMockType { return { type: 'Literal', value, diff --git a/__mocks__/genInteractives.js b/__mocks__/genInteractives.js index 6f8ec847d..4c57fc838 100644 --- a/__mocks__/genInteractives.js +++ b/__mocks__/genInteractives.js @@ -7,7 +7,8 @@ import includes from 'array-includes'; import JSXAttributeMock from './JSXAttributeMock'; import JSXElementMock from './JSXElementMock'; -import type { TJSXElementMock } from './JSXElementMock'; +import type { JSXElementMockType } from './JSXElementMock'; +import type { JSXAttributeMockType } from './JSXAttributeMock'; const domElements = [...dom.keys()]; const roleNames = [...roles.keys()]; @@ -147,7 +148,7 @@ const nonInteractiveRoles = roleNames // aria-activedescendant, thus in practice we treat it as a widget. .filter((role) => !includes(['toolbar'], role)); -export function genElementSymbol(openingElement: Object) { +export function genElementSymbol(openingElement: Object): string { return ( openingElement.name.name + (openingElement.attributes.length > 0 ? `${openingElement.attributes @@ -158,8 +159,8 @@ export function genElementSymbol(openingElement: Object) { ); } -export function genInteractiveElements(): Array { - return Object.keys(interactiveElementsMap).map((elementSymbol: string): TJSXElementMock => { +export function genInteractiveElements(): Array { + return Object.keys(interactiveElementsMap).map((elementSymbol: string): JSXElementMockType => { const bracketIndex = elementSymbol.indexOf('['); let name = elementSymbol; if (bracketIndex > -1) { @@ -170,15 +171,15 @@ export function genInteractiveElements(): Array { }); } -export function genInteractiveRoleElements(): Array { - return [...interactiveRoles, 'button article', 'fakerole button article'].map((value): TJSXElementMock => JSXElementMock( +export function genInteractiveRoleElements(): Array { + return [...interactiveRoles, 'button article', 'fakerole button article'].map((value): JSXElementMockType => JSXElementMock( 'div', [JSXAttributeMock('role', value)], )); } -export function genNonInteractiveElements(): Array { - return Object.keys(nonInteractiveElementsMap).map((elementSymbol): TJSXElementMock => { +export function genNonInteractiveElements(): Array { + return Object.keys(nonInteractiveElementsMap).map((elementSymbol): JSXElementMockType => { const bracketIndex = elementSymbol.indexOf('['); let name = elementSymbol; if (bracketIndex > -1) { @@ -189,7 +190,7 @@ export function genNonInteractiveElements(): Array { }); } -export function genNonInteractiveRoleElements() { +export function genNonInteractiveRoleElements(): Array { return [ ...nonInteractiveRoles, 'article button', @@ -197,17 +198,17 @@ export function genNonInteractiveRoleElements() { ].map((value) => JSXElementMock('div', [JSXAttributeMock('role', value)])); } -export function genAbstractRoleElements() { +export function genAbstractRoleElements(): Array { return abstractRoles.map((value) => JSXElementMock('div', [JSXAttributeMock('role', value)])); } -export function genNonAbstractRoleElements() { +export function genNonAbstractRoleElements(): Array { return nonAbstractRoles.map((value) => JSXElementMock('div', [JSXAttributeMock('role', value)])); } -export function genIndeterminantInteractiveElements(): Array { +export function genIndeterminantInteractiveElements(): Array { return Object.keys(indeterminantInteractiveElementsMap).map((name) => { - const attributes = indeterminantInteractiveElementsMap[name].map(({ prop, value }): TJSXElementMock => JSXAttributeMock(prop, value)); + const attributes = indeterminantInteractiveElementsMap[name].map(({ prop, value }): JSXAttributeMockType => JSXAttributeMock(prop, value)); return JSXElementMock(name, attributes); }); } diff --git a/__tests__/__util__/ruleOptionsMapperFactory.js b/__tests__/__util__/ruleOptionsMapperFactory.js index 5f506088d..7175bf91b 100644 --- a/__tests__/__util__/ruleOptionsMapperFactory.js +++ b/__tests__/__util__/ruleOptionsMapperFactory.js @@ -9,7 +9,11 @@ type ESLintTestRunnerTestCase = { parserOptions: ?Array }; -export default function ruleOptionsMapperFactory(ruleOptions: Array = []) { +type RuleOptionsMapperFactoryType = ( + params: ESLintTestRunnerTestCase +) => ESLintTestRunnerTestCase; + +export default function ruleOptionsMapperFactory(ruleOptions: Array = []): RuleOptionsMapperFactoryType { // eslint-disable-next-line return ({ code, errors, options, parserOptions }: ESLintTestRunnerTestCase): ESLintTestRunnerTestCase => { return { diff --git a/flow/eslint.js b/flow/eslint.js index 9d20c5cdb..ff3df7ed3 100644 --- a/flow/eslint.js +++ b/flow/eslint.js @@ -10,3 +10,12 @@ export type ESLintContext = { options: Array, report: (ESLintReport) => void, }; + +export type ESLintConfig = { + meta?: {[string]: mixed}, + create: (context: ESLintContext) => mixed, +} + +export type ESLintVisitorSelectorConfig = { + [string]: mixed, +}; diff --git a/src/rules/anchor-is-valid.js b/src/rules/anchor-is-valid.js index 2afd3c7a6..ebcefe21a 100644 --- a/src/rules/anchor-is-valid.js +++ b/src/rules/anchor-is-valid.js @@ -10,7 +10,7 @@ import { elementType, getProp, getPropValue } from 'jsx-ast-utils'; import type { JSXOpeningElement } from 'ast-types-flow'; -import type { ESLintContext } from '../../flow/eslint'; +import type { ESLintConfig, ESLintContext, ESLintVisitorSelectorConfig } from '../../flow/eslint'; import { generateObjSchema, arraySchema, enumArraySchema } from '../util/schemas'; const allAspects = ['noHref', 'invalidHref', 'preferButton']; @@ -27,7 +27,7 @@ const schema = generateObjSchema({ aspects: enumArraySchema(allAspects, 1), }); -module.exports = { +module.exports = ({ meta: { docs: { url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/anchor-is-valid.md', @@ -35,8 +35,8 @@ module.exports = { schema: [schema], }, - create: (context: ESLintContext) => ({ - JSXOpeningElement: (node: JSXOpeningElement) => { + create: (context: ESLintContext): ESLintVisitorSelectorConfig => ({ + JSXOpeningElement: (node: JSXOpeningElement): void => { const { attributes } = node; const options = context.options[0] || {}; const componentOptions = options.components || []; @@ -115,4 +115,4 @@ module.exports = { } }, }), -}; +}: ESLintConfig); diff --git a/src/rules/control-has-associated-label.js b/src/rules/control-has-associated-label.js index 481aa3bb0..411799b60 100644 --- a/src/rules/control-has-associated-label.js +++ b/src/rules/control-has-associated-label.js @@ -13,7 +13,7 @@ import { elementType, getProp, getLiteralPropValue } from 'jsx-ast-utils'; import type { JSXElement } from 'ast-types-flow'; import includes from 'array-includes'; import { generateObjSchema, arraySchema } from '../util/schemas'; -import type { ESLintContext } from '../../flow/eslint'; +import type { ESLintConfig, ESLintContext, ESLintVisitorSelectorConfig } from '../../flow/eslint'; import isDOMElement from '../util/isDOMElement'; import isHiddenFromScreenReader from '../util/isHiddenFromScreenReader'; import isInteractiveElement from '../util/isInteractiveElement'; @@ -36,13 +36,13 @@ const schema = generateObjSchema({ }, }); -module.exports = { +module.exports = ({ meta: { docs: {}, schema: [schema], }, - create: (context: ESLintContext) => { + create: (context: ESLintContext): ESLintVisitorSelectorConfig => { const options = context.options[0] || {}; const { labelAttributes = [], @@ -53,7 +53,7 @@ module.exports = { const newIgnoreElements = new Set([...ignoreElements, ...ignoreList]); - const rule = (node: JSXElement) => { + const rule = (node: JSXElement): void => { const tag = elementType(node.openingElement); const role = getLiteralPropValue(getProp(node.openingElement.attributes, 'role')); // Ignore interactive elements that might get their label from a source @@ -112,4 +112,4 @@ module.exports = { JSXElement: rule, }; }, -}; +}: ESLintConfig); diff --git a/src/rules/interactive-supports-focus.js b/src/rules/interactive-supports-focus.js index 25829b76c..708713a46 100644 --- a/src/rules/interactive-supports-focus.js +++ b/src/rules/interactive-supports-focus.js @@ -17,7 +17,7 @@ import { } from 'jsx-ast-utils'; import type { JSXOpeningElement } from 'ast-types-flow'; import includes from 'array-includes'; -import type { ESLintContext } from '../../flow/eslint'; +import type { ESLintConfig, ESLintContext, ESLintVisitorSelectorConfig } from '../../flow/eslint'; import { enumArraySchema, generateObjSchema, @@ -47,7 +47,7 @@ const interactiveProps = [ ...eventHandlersByType.keyboard, ]; -module.exports = { +module.exports = ({ meta: { docs: { url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/interactive-supports-focus.md', @@ -55,11 +55,7 @@ module.exports = { schema: [schema], }, - create: (context: ESLintContext & { - options: { - tabbable: Array - } - }) => ({ + create: (context: ESLintContext): ESLintVisitorSelectorConfig => ({ JSXOpeningElement: (node: JSXOpeningElement) => { const tabbable = ( context.options && context.options[0] && context.options[0].tabbable @@ -111,4 +107,4 @@ module.exports = { } }, }), -}; +}: ESLintConfig); diff --git a/src/rules/label-has-associated-control.js b/src/rules/label-has-associated-control.js index 6d78cbf65..ee1afb96c 100644 --- a/src/rules/label-has-associated-control.js +++ b/src/rules/label-has-associated-control.js @@ -12,7 +12,7 @@ import { getProp, getPropValue, elementType } from 'jsx-ast-utils'; import type { JSXElement } from 'ast-types-flow'; import { generateObjSchema, arraySchema } from '../util/schemas'; -import type { ESLintContext } from '../../flow/eslint'; +import type { ESLintConfig, ESLintContext, ESLintVisitorSelectorConfig } from '../../flow/eslint'; import mayContainChildComponent from '../util/mayContainChildComponent'; import mayHaveAccessibleLabel from '../util/mayHaveAccessibleLabel'; @@ -41,13 +41,13 @@ const validateId = (node) => { return htmlForAttr !== false && !!htmlForValue; }; -module.exports = { +module.exports = ({ meta: { docs: {}, schema: [schema], }, - create: (context: ESLintContext) => { + create: (context: ESLintContext): ESLintVisitorSelectorConfig => { const options = context.options[0] || {}; const labelComponents = options.labelComponents || []; const assertType = options.assert || 'either'; @@ -122,4 +122,4 @@ module.exports = { JSXElement: rule, }; }, -}; +}: ESLintConfig); diff --git a/src/rules/media-has-caption.js b/src/rules/media-has-caption.js index 3e732dd22..5b3081473 100644 --- a/src/rules/media-has-caption.js +++ b/src/rules/media-has-caption.js @@ -8,9 +8,9 @@ // Rule Definition // ---------------------------------------------------------------------------- -import type { JSXElement, JSXOpeningElement } from 'ast-types-flow'; +import type { JSXElement, JSXOpeningElement, Node } from 'ast-types-flow'; import { elementType, getProp, getLiteralPropValue } from 'jsx-ast-utils'; -import type { ESLintContext } from '../../flow/eslint'; +import type { ESLintConfig, ESLintContext, ESLintVisitorSelectorConfig } from '../../flow/eslint'; import { generateObjSchema, arraySchema } from '../util/schemas'; const errorMessage = 'Media elements such as