diff --git a/package.json b/package.json index 9d43e2ac..6528a6be 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ }, "description": "Flowtype linting rules for ESLint.", "devDependencies": { + "ajv": "^5.2.1", "babel-cli": "^6.14.0", "babel-eslint": "^6.1.2", "babel-plugin-add-module-exports": "^0.2.1", diff --git a/src/index.js b/src/index.js index a5483a49..27f304e7 100644 --- a/src/index.js +++ b/src/index.js @@ -1,28 +1,28 @@ import _ from 'lodash'; -import {checkFlowFileAnnotation} from './utilities'; +import recommended from './configs/recommended.json'; +import booleanStyle from './rules/booleanStyle'; import defineFlowType from './rules/defineFlowType'; +import delimiterDangle from './rules/delimiterDangle'; import genericSpacing from './rules/genericSpacing'; -import noWeakTypes from './rules/noWeakTypes'; +import noDupeKeys from './rules/noDupeKeys'; +import noPrimitiveConstructorTypes from './rules/noPrimitiveConstructorTypes'; import noTypesMissingFileAnnotation from './rules/noTypesMissingFileAnnotation'; +import noWeakTypes from './rules/noWeakTypes'; +import objectTypeDelimiter from './rules/objectTypeDelimiter'; import requireParameterType from './rules/requireParameterType'; import requireReturnType from './rules/requireReturnType'; import requireValidFileAnnotation from './rules/requireValidFileAnnotation'; import requireVariableType from './rules/requireVariableType'; import semi from './rules/semi'; +import sortKeys from './rules/sortKeys'; import spaceAfterTypeColon from './rules/spaceAfterTypeColon'; import spaceBeforeGenericBracket from './rules/spaceBeforeGenericBracket'; import spaceBeforeTypeColon from './rules/spaceBeforeTypeColon'; -import unionIntersectionSpacing from './rules/unionIntersectionSpacing'; import typeIdMatch from './rules/typeIdMatch'; +import unionIntersectionSpacing from './rules/unionIntersectionSpacing'; import useFlowType from './rules/useFlowType'; import validSyntax from './rules/validSyntax'; -import booleanStyle from './rules/booleanStyle'; -import delimiterDangle from './rules/delimiterDangle'; -import noDupeKeys from './rules/noDupeKeys'; -import noPrimitiveConstructorTypes from './rules/noPrimitiveConstructorTypes'; -import sortKeys from './rules/sortKeys'; -import objectTypeDelimiter from './rules/objectTypeDelimiter'; -import recommended from './configs/recommended.json'; +import {checkFlowFileAnnotation} from './utilities'; const rules = { 'boolean-style': booleanStyle, @@ -54,19 +54,14 @@ export default { recommended }, rules: _.mapValues(rules, (rule, key) => { - // Support current and deprecated rule formats - if (_.isPlainObject(rule)) { - return { - ...rule, - create: _.partial(checkFlowFileAnnotation, rule.create) - }; - } - if (key === 'no-types-missing-file-annotation') { return rule; } - return _.partial(checkFlowFileAnnotation, rule); + return { + ...rule, + create: _.partial(checkFlowFileAnnotation, rule.create) + }; }), rulesConfig: { 'boolean-style': 0, diff --git a/src/rules/booleanStyle.js b/src/rules/booleanStyle.js index 9ddad7b9..e484a06a 100644 --- a/src/rules/booleanStyle.js +++ b/src/rules/booleanStyle.js @@ -1,4 +1,11 @@ -export default (context) => { +const schema = [ + { + enum: ['bool', 'boolean'], + type: 'string' + } +]; + +const create = (context) => { const longForm = (context.options[0] || 'boolean') === 'boolean'; return { @@ -27,3 +34,8 @@ export default (context) => { } }; }; + +export default { + create, + schema +}; diff --git a/src/rules/defineFlowType.js b/src/rules/defineFlowType.js index 27e76a58..eb16459e 100644 --- a/src/rules/defineFlowType.js +++ b/src/rules/defineFlowType.js @@ -1,6 +1,6 @@ -export const schema = []; +const schema = []; -export default (context) => { +const create = (context) => { let globalScope; // do nearly the same thing that eslint does for config globals @@ -62,3 +62,8 @@ export default (context) => { } }; }; + +export default { + create, + schema +}; diff --git a/src/rules/delimiterDangle.js b/src/rules/delimiterDangle.js index a71cd7b6..1351b872 100644 --- a/src/rules/delimiterDangle.js +++ b/src/rules/delimiterDangle.js @@ -1,6 +1,13 @@ import _ from 'lodash'; -export default (context) => { +const schema = [ + { + enum: ['always', 'always-multiline', 'only-multiline', 'never'], + type: 'string' + } +]; + +const create = (context) => { const option = context.options[0] || 'never'; const sourceCode = context.getSourceCode(); @@ -103,3 +110,8 @@ export default (context) => { } }; }; + +export default { + create, + schema +}; diff --git a/src/rules/genericSpacing.js b/src/rules/genericSpacing.js index e438e4f2..dcee2275 100644 --- a/src/rules/genericSpacing.js +++ b/src/rules/genericSpacing.js @@ -1,6 +1,13 @@ import {spacingFixers} from './../utilities'; -export default (context) => { +const schema = [ + { + enum: ['always', 'never'], + type: 'string' + } +]; + +const create = (context) => { const sourceCode = context.getSourceCode(); const never = (context.options[0] || 'never') === 'never'; @@ -76,3 +83,8 @@ export default (context) => { } }; }; + +export default { + create, + schema +}; diff --git a/src/rules/noDupeKeys.js b/src/rules/noDupeKeys.js index 90132ef2..fd4ebdae 100644 --- a/src/rules/noDupeKeys.js +++ b/src/rules/noDupeKeys.js @@ -3,7 +3,9 @@ import { getParameterName } from './../utilities'; -export default (context) => { +const schema = []; + +const create = (context) => { const report = (node) => { context.report({ loc: node.loc, @@ -83,3 +85,8 @@ export default (context) => { ObjectTypeAnnotation: checkForDuplicates }; }; + +export default { + create, + schema +}; diff --git a/src/rules/noPrimitiveConstructorTypes.js b/src/rules/noPrimitiveConstructorTypes.js index 6c1c9efb..6cdb2220 100644 --- a/src/rules/noPrimitiveConstructorTypes.js +++ b/src/rules/noPrimitiveConstructorTypes.js @@ -1,24 +1,27 @@ import _ from 'lodash'; -export default { - create: (context) => { - return { - GenericTypeAnnotation: (node) => { - const name = _.get(node, 'id.name'); +const schema = []; + +const create = (context) => { + return { + GenericTypeAnnotation: (node) => { + const name = _.get(node, 'id.name'); - if (RegExp(/^(Boolean|Number|String)$/).test(name)) { - context.report({ - data: { - name - }, - loc: node.loc, - message: 'Unexpected use of {{name}} constructor type.', - node - }); - } + if (RegExp(/^(Boolean|Number|String)$/).test(name)) { + context.report({ + data: { + name + }, + loc: node.loc, + message: 'Unexpected use of {{name}} constructor type.', + node + }); } - }; - }, - meta: {}, - schema: {} + } + }; +}; + +export default { + create, + schema }; diff --git a/src/rules/noTypesMissingFileAnnotation.js b/src/rules/noTypesMissingFileAnnotation.js index b6823c1f..b96906b0 100644 --- a/src/rules/noTypesMissingFileAnnotation.js +++ b/src/rules/noTypesMissingFileAnnotation.js @@ -5,7 +5,9 @@ import {isFlowFile} from '../utilities'; * Only checks files without a valid flow annotation. */ -export default (context) => { +const schema = []; + +const create = (context) => { // Skip flow files if (isFlowFile(context, false)) { return {}; @@ -42,3 +44,8 @@ export default (context) => { } }; }; + +export default { + create, + schema +}; diff --git a/src/rules/noWeakTypes.js b/src/rules/noWeakTypes.js index bc996088..18ca85a8 100644 --- a/src/rules/noWeakTypes.js +++ b/src/rules/noWeakTypes.js @@ -1,5 +1,23 @@ import _ from 'lodash'; +const schema = [ + { + additionalProperties: false, + properties: { + Function: { + type: 'boolean' + }, + Object: { + type: 'boolean' + }, + any: { + type: 'boolean' + } + }, + type: 'object' + } +]; + const reportWeakType = (context, weakType) => { return (node) => { context.report({ @@ -20,7 +38,7 @@ const genericTypeEvaluator = (context, {checkFunction, checkObject}) => { }; }; -export default (context) => { +const create = (context) => { const checkAny = _.get(context, 'options[0].any', true) === true; const checkFunction = _.get(context, 'options[0].Function', true) === true; const checkObject = _.get(context, 'options[0].Object', true) === true; @@ -40,3 +58,8 @@ export default (context) => { return checks; }; + +export default { + create, + schema +}; diff --git a/src/rules/objectTypeDelimiter.js b/src/rules/objectTypeDelimiter.js index d6265f00..7d801063 100644 --- a/src/rules/objectTypeDelimiter.js +++ b/src/rules/objectTypeDelimiter.js @@ -49,7 +49,8 @@ const create = (context) => { const schema = [ { - enum: ['semicolon', 'comma'] + enum: ['semicolon', 'comma'], + type: 'string' } ]; diff --git a/src/rules/requireParameterType.js b/src/rules/requireParameterType.js index 3cc184f9..9f2678db 100644 --- a/src/rules/requireParameterType.js +++ b/src/rules/requireParameterType.js @@ -5,7 +5,22 @@ import { quoteName } from './../utilities'; -export default iterateFunctionNodes((context) => { +const schema = [ + { + additionalProperties: false, + properties: { + excludeArrowFunctions: { + enum: [false, true, 'expressionsOnly'] + }, + excludeParameterMatch: { + type: 'string' + } + }, + type: 'object' + } +]; + +const create = iterateFunctionNodes((context) => { const skipArrows = _.get(context, 'options[0].excludeArrowFunctions'); const excludeParameterMatch = new RegExp(_.get(context, 'options[0].excludeParameterMatch', 'a^')); @@ -38,3 +53,8 @@ export default iterateFunctionNodes((context) => { }); }; }); + +export default { + create, + schema +}; diff --git a/src/rules/requireReturnType.js b/src/rules/requireReturnType.js index 7f029d04..c6ecfbf8 100644 --- a/src/rules/requireReturnType.js +++ b/src/rules/requireReturnType.js @@ -1,6 +1,38 @@ import _ from 'lodash'; -export default (context) => { +const schema = [ + { + enum: ['always'], + type: 'string' + }, + { + additionalProperties: false, + properties: { + annotateUndefined: { + enum: ['always', 'never'], + type: 'string' + }, + excludeArrowFunctions: { + enum: [false, true, 'expressionsOnly'] + }, + excludeMatching: { + items: { + type: 'string' + }, + type: 'array' + }, + includeOnlyMatching: { + items: { + type: 'string' + }, + type: 'array' + } + }, + type: 'object' + } +]; + +const create = (context) => { const annotateReturn = (_.get(context, 'options[0]') || 'always') === 'always'; const annotateUndefined = (_.get(context, 'options[1].annotateUndefined') || 'never') === 'always'; const skipArrows = _.get(context, 'options[1].excludeArrowFunctions') || false; @@ -100,3 +132,8 @@ export default (context) => { } }; }; + +export default { + create, + schema +}; diff --git a/src/rules/requireValidFileAnnotation.js b/src/rules/requireValidFileAnnotation.js index 911d59ef..9db7d623 100644 --- a/src/rules/requireValidFileAnnotation.js +++ b/src/rules/requireValidFileAnnotation.js @@ -24,13 +24,24 @@ const checkAnnotationSpelling = (comment) => { return /@[a-z]+\b/.test(comment) && fuzzyStringMatch(comment.replace(/no/i, ''), '@flow', 0.20); }; -export const schema = [ +const schema = [ { - enum: ['always'] + enum: ['always', 'never'], + type: 'string' + }, + { + additionalProperties: false, + properties: { + annotationStyle: { + enum: ['none', 'line', 'block'], + type: 'string' + } + }, + type: 'object' } ]; -export default (context) => { +const create = (context) => { const always = context.options[0] === 'always'; const style = _.get(context, 'options[1].annotationStyle', defaults.annotationStyle); @@ -64,3 +75,8 @@ export default (context) => { } }; }; + +export default { + create, + schema +}; diff --git a/src/rules/requireVariableType.js b/src/rules/requireVariableType.js index 73264508..d5c5cdb5 100644 --- a/src/rules/requireVariableType.js +++ b/src/rules/requireVariableType.js @@ -4,7 +4,34 @@ import { quoteName } from './../utilities'; -export default (context) => { +const schema = [ + { + additionalProperties: false, + properties: { + excludeVariableMatch: { + type: 'string' + }, + excludeVariableTypes: { + additionalProperties: false, + properties: { + const: { + type: 'boolean' + }, + let: { + type: 'boolean' + }, + var: { + type: 'boolean' + } + }, + type: 'object' + } + }, + type: 'object' + } +]; + +const create = (context) => { const checkThisFile = !_.get(context, 'settings.flowtype.onlyFilesWithFlowAnnotation') || isFlowFile(context); if (!checkThisFile) { @@ -45,3 +72,8 @@ export default (context) => { } }; }; + +export default { + create, + schema +}; diff --git a/src/rules/semi.js b/src/rules/semi.js index 1dc6ddf4..33664666 100644 --- a/src/rules/semi.js +++ b/src/rules/semi.js @@ -1,4 +1,11 @@ -export default (context) => { +const schema = [ + { + enum: ['always', 'never'], + type: 'string' + } +]; + +const create = (context) => { const never = (context.options[0] || 'always') === 'never'; const sourceCode = context.getSourceCode(); @@ -50,3 +57,8 @@ export default (context) => { TypeAlias: checkForSemicolon }; }; + +export default { + create, + schema +}; diff --git a/src/rules/sortKeys.js b/src/rules/sortKeys.js index 43d20121..5570c3d1 100644 --- a/src/rules/sortKeys.js +++ b/src/rules/sortKeys.js @@ -8,6 +8,25 @@ const defaults = { natural: false }; +const schema = [ + { + enum: ['asc', 'desc'], + type: 'string' + }, + { + additionalProperties: false, + properties: { + caseSensitive: { + type: 'boolean' + }, + natural: { + type: 'boolean' + } + }, + type: 'object' + } +]; + /** * Functions to compare the order of two strings * @@ -46,7 +65,7 @@ const isValidOrders = { } }; -export default (context) => { +const create = (context) => { const order = _.get(context, ['options', 0], 'asc'); const {natural, caseSensitive} = _.get(context, ['options', 1], defaults); const insensitive = caseSensitive === false; @@ -89,3 +108,8 @@ export default (context) => { ObjectTypeAnnotation: checkKeyOrder }; }; + +export default { + create, + schema +}; diff --git a/src/rules/spaceAfterTypeColon.js b/src/rules/spaceAfterTypeColon.js index c1d5314f..25085d94 100644 --- a/src/rules/spaceAfterTypeColon.js +++ b/src/rules/spaceAfterTypeColon.js @@ -1,9 +1,30 @@ import _ from 'lodash'; import makeSpacing from './typeColonSpacing'; -export default (context) => { +const schema = [ + { + enum: ['always', 'never'], + type: 'string' + }, + { + additionalProperties: false, + properties: { + allowLineBreak: { + type: 'boolean' + } + }, + type: 'object' + } +]; + +const create = (context) => { return makeSpacing('after', context, { allowLineBreak: _.get(context, ['options', '1', 'allowLineBreak'], false), always: _.get(context, ['options', '0'], 'always') === 'always' }); }; + +export default { + create, + schema +}; diff --git a/src/rules/spaceBeforeGenericBracket.js b/src/rules/spaceBeforeGenericBracket.js index f0902d0f..71a0547c 100644 --- a/src/rules/spaceBeforeGenericBracket.js +++ b/src/rules/spaceBeforeGenericBracket.js @@ -1,6 +1,13 @@ import {spacingFixers} from '../utilities'; -export default (context) => { +const schema = [ + { + enum: ['always', 'never'], + type: 'string' + } +]; + +const create = (context) => { const never = (context.options[0] || 'never') === 'never'; return { @@ -45,3 +52,8 @@ export default (context) => { } }; }; + +export default { + create, + schema +}; diff --git a/src/rules/spaceBeforeTypeColon.js b/src/rules/spaceBeforeTypeColon.js index b199063e..4515bb60 100644 --- a/src/rules/spaceBeforeTypeColon.js +++ b/src/rules/spaceBeforeTypeColon.js @@ -1,7 +1,19 @@ import makeSpacing from './typeColonSpacing'; -export default (context) => { +const schema = [ + { + enum: ['always', 'never'], + type: 'string' + } +]; + +const create = (context) => { return makeSpacing('before', context, { always: context.options[0] === 'always' }); }; + +export default { + create, + schema +}; diff --git a/src/rules/typeIdMatch.js b/src/rules/typeIdMatch.js index a3d5a8df..c9ac83d7 100644 --- a/src/rules/typeIdMatch.js +++ b/src/rules/typeIdMatch.js @@ -1,10 +1,10 @@ -export const schema = [ +const schema = [ { type: 'string' } ]; -export default (context) => { +const create = (context) => { const pattern = new RegExp(context.options[0] || '^([A-Z][a-z0-9]*)+Type$'); return { @@ -20,3 +20,8 @@ export default (context) => { } }; }; + +export default { + create, + schema +}; diff --git a/src/rules/unionIntersectionSpacing.js b/src/rules/unionIntersectionSpacing.js index 5295798b..76b9d28f 100644 --- a/src/rules/unionIntersectionSpacing.js +++ b/src/rules/unionIntersectionSpacing.js @@ -1,6 +1,13 @@ import {spacingFixers, getTokenAfterParens} from '../utilities'; -export default (context) => { +const schema = [ + { + enum: ['always', 'never'], + type: 'string' + } +]; + +const create = (context) => { const sourceCode = context.getSourceCode(); const always = (context.options[0] || 'always') === 'always'; @@ -65,3 +72,8 @@ export default (context) => { UnionTypeAnnotation: check }; }; + +export default { + create, + schema +}; diff --git a/src/rules/useFlowType.js b/src/rules/useFlowType.js index 947fc7eb..185dad40 100644 --- a/src/rules/useFlowType.js +++ b/src/rules/useFlowType.js @@ -1,6 +1,6 @@ -export const schema = []; +const schema = []; -export default (context) => { +const create = (context) => { const markTypeAsUsed = (node) => { context.markVariableAsUsed(node.id.name); }; @@ -34,3 +34,8 @@ export default (context) => { } }; }; + +export default { + create, + schema +}; diff --git a/src/rules/validSyntax.js b/src/rules/validSyntax.js index 7a8e0040..25333949 100644 --- a/src/rules/validSyntax.js +++ b/src/rules/validSyntax.js @@ -5,6 +5,8 @@ import { quoteName } from './../utilities'; +const schema = []; + const create = iterateFunctionNodes((context) => { return (functionNode) => { _.forEach(functionNode.params, (identifierNode) => { @@ -30,6 +32,7 @@ export default { create, meta: { deprecated: true - } + }, + schema }; diff --git a/tests/rules/assertions/booleanStyle.js b/tests/rules/assertions/booleanStyle.js index f49d12be..2a9202fd 100644 --- a/tests/rules/assertions/booleanStyle.js +++ b/tests/rules/assertions/booleanStyle.js @@ -18,6 +18,37 @@ export default { output: 'type X = bool' } ], + misconfigured: [ + { + errors: [ + { + data: 'integer', + dataPath: '[0]', + keyword: 'enum', + message: 'should be equal to one of the allowed values', + params: { + allowedValues: [ + 'bool', + 'boolean' + ] + }, + parentSchema: { + enum: [ + 'bool', + 'boolean' + ], + type: 'string' + }, + schema: [ + 'bool', + 'boolean' + ], + schemaPath: '#/items/0/enum' + } + ], + options: ['integer'] + } + ], valid: [ { code: 'type X = boolean' diff --git a/tests/rules/assertions/delimiterDangle.js b/tests/rules/assertions/delimiterDangle.js index f4f2344d..dd590ea9 100644 --- a/tests/rules/assertions/delimiterDangle.js +++ b/tests/rules/assertions/delimiterDangle.js @@ -500,11 +500,51 @@ const TUPLE_TYPE_ANNOTATION = { ] }; +const MISCONFIGURED = [ + { + errors: [ + { + data: 'occasionally', + dataPath: '[0]', + keyword: 'enum', + message: 'should be equal to one of the allowed values', + params: { + allowedValues: [ + 'always', + 'always-multiline', + 'only-multiline', + 'never' + ] + }, + parentSchema: { + enum: [ + 'always', + 'always-multiline', + 'only-multiline', + 'never' + ], + type: 'string' + }, + schema: [ + 'always', + 'always-multiline', + 'only-multiline', + 'never' + ], + schemaPath: '#/items/0/enum' + + } + ], + options: ['occasionally'] + } +]; + export default { invalid: [ ...OBJECT_TYPE_ANNOTATION.invalid, ...TUPLE_TYPE_ANNOTATION.invalid ], + misconfigured: MISCONFIGURED, valid: [ ...OBJECT_TYPE_ANNOTATION.valid, ...TUPLE_TYPE_ANNOTATION.valid diff --git a/tests/rules/assertions/genericSpacing.js b/tests/rules/assertions/genericSpacing.js index 727f5bb1..e67d68f3 100644 --- a/tests/rules/assertions/genericSpacing.js +++ b/tests/rules/assertions/genericSpacing.js @@ -92,6 +92,37 @@ export default { output: 'type X = Promise< (foo), bar, (((baz))) >' } ], + misconfigured: [ + { + errors: [ + { + data: 'frequently', + dataPath: '[0]', + keyword: 'enum', + message: 'should be equal to one of the allowed values', + params: { + allowedValues: [ + 'always', + 'never' + ] + }, + parentSchema: { + enum: [ + 'always', + 'never' + ], + type: 'string' + }, + schema: [ + 'always', + 'never' + ], + schemaPath: '#/items/0/enum' + } + ], + options: ['frequently'] + } + ], valid: [ // Never diff --git a/tests/rules/assertions/noTypesMissingFileAnnotation.js b/tests/rules/assertions/noTypesMissingFileAnnotation.js index d0368763..a8aec639 100644 --- a/tests/rules/assertions/noTypesMissingFileAnnotation.js +++ b/tests/rules/assertions/noTypesMissingFileAnnotation.js @@ -41,6 +41,17 @@ export default { errors: [{ message: 'Type annotations require valid Flow declaration.' }] + }, + { + code: 'const x: number = 42;', + errors: [{ + message: 'Type annotations require valid Flow declaration.' + }], + settings: { + flowtype: { + onlyFilesWithFlowAnnotation: true + } + } } ], valid: [ diff --git a/tests/rules/assertions/noWeakTypes.js b/tests/rules/assertions/noWeakTypes.js index 1f0730ea..59e7cd22 100644 --- a/tests/rules/assertions/noWeakTypes.js +++ b/tests/rules/assertions/noWeakTypes.js @@ -193,6 +193,60 @@ export default { }] } ], + misconfigured: [ + { + errors: [ + { + data: { + nonExistentWeakType: false + }, + dataPath: '[0]', + keyword: 'additionalProperties', + message: 'should NOT have additional properties', + params: { + additionalProperty: 'nonExistentWeakType' + }, + parentSchema: { + additionalProperties: false, + properties: { + Function: { + type: 'boolean' + }, + Object: { + type: 'boolean' + }, + any: { + type: 'boolean' + } + }, + type: 'object' + }, + schema: false, + schemaPath: '#/items/0/additionalProperties' + } + ], + options: [{nonExistentWeakType: false}] + }, + { + errors: [ + { + data: 'irrelevant', + dataPath: '[0].Object', + keyword: 'type', + message: 'should be boolean', + params: { + type: 'boolean' + }, + parentSchema: { + type: 'boolean' + }, + schema: 'boolean', + schemaPath: '#/items/0/properties/Object/type' + } + ], + options: [{Object: 'irrelevant'}] + } + ], valid: [ { code: 'function foo(thing): string {}' diff --git a/tests/rules/assertions/objectTypeDelimiter.js b/tests/rules/assertions/objectTypeDelimiter.js index 19fa02fc..21a403dd 100644 --- a/tests/rules/assertions/objectTypeDelimiter.js +++ b/tests/rules/assertions/objectTypeDelimiter.js @@ -85,6 +85,37 @@ export default { output: 'declare class Foo { static (): Foo, }' } ], + misconfigured: [ + { + errors: [ + { + data: 'period', + dataPath: '[0]', + keyword: 'enum', + message: 'should be equal to one of the allowed values', + params: { + allowedValues: [ + 'semicolon', + 'comma' + ] + }, + parentSchema: { + enum: [ + 'semicolon', + 'comma' + ], + type: 'string' + }, + schema: [ + 'semicolon', + 'comma' + ], + schemaPath: '#/items/0/enum' + } + ], + options: ['period'] + } + ], valid: [ { code: 'type Foo = { a: Foo; b: Bar }', diff --git a/tests/rules/assertions/requireParameterType.js b/tests/rules/assertions/requireParameterType.js index aefb0813..63e02891 100644 --- a/tests/rules/assertions/requireParameterType.js +++ b/tests/rules/assertions/requireParameterType.js @@ -135,6 +135,92 @@ export default { ] } ], + misconfigured: [ + { + errors: [ + { + data: { + excludeOtherStuff: true + }, + dataPath: '[0]', + keyword: 'additionalProperties', + message: 'should NOT have additional properties', + params: { + additionalProperty: 'excludeOtherStuff' + }, + parentSchema: { + additionalProperties: false, + properties: { + excludeArrowFunctions: { + enum: [ + false, + true, + 'expressionsOnly' + ] + }, + excludeParameterMatch: { + type: 'string' + } + }, + type: 'object' + }, + schema: false, + schemaPath: '#/items/0/additionalProperties' + } + ], + options: [{excludeOtherStuff: true}] + }, + { + errors: [ + { + data: 'everything', + dataPath: '[0].excludeArrowFunctions', + keyword: 'enum', + message: 'should be equal to one of the allowed values', + params: { + allowedValues: [ + false, + true, + 'expressionsOnly' + ] + }, + parentSchema: { + enum: [ + false, + true, + 'expressionsOnly' + ] + }, + schema: [ + false, + true, + 'expressionsOnly' + ], + schemaPath: '#/items/0/properties/excludeArrowFunctions/enum' + } + ], + options: [{excludeArrowFunctions: 'everything'}] + }, + { + errors: [ + { + data: 3, + dataPath: '[0].excludeParameterMatch', + keyword: 'type', + message: 'should be string', + params: { + type: 'string' + }, + parentSchema: { + type: 'string' + }, + schema: 'string', + schemaPath: '#/items/0/properties/excludeParameterMatch/type' + } + ], + options: [{excludeParameterMatch: 3}] + } + ], valid: [ { code: '(foo: string) => {}' diff --git a/tests/rules/assertions/requireReturnType.js b/tests/rules/assertions/requireReturnType.js index ce6d635e..ff8fcffb 100644 --- a/tests/rules/assertions/requireReturnType.js +++ b/tests/rules/assertions/requireReturnType.js @@ -308,6 +308,185 @@ export default { ] } ], + misconfigured: [ + { + errors: [ + { + data: 'never', + dataPath: '[0]', + keyword: 'enum', + message: 'should be equal to one of the allowed values', + params: { + allowedValues: [ + 'always' + ] + }, + parentSchema: { + enum: [ + 'always' + ], + type: 'string' + }, + schema: [ + 'always' + ], + schemaPath: '#/items/0/enum' + } + ], + options: ['never'] + }, + { + errors: [ + { + data: { + excludeOtherStuff: true + }, + dataPath: '[1]', + keyword: 'additionalProperties', + message: 'should NOT have additional properties', + params: { + additionalProperty: 'excludeOtherStuff' + }, + parentSchema: { + additionalProperties: false, + properties: { + annotateUndefined: { + enum: [ + 'always', + 'never' + ], + type: 'string' + }, + excludeArrowFunctions: { + enum: [ + false, + true, + 'expressionsOnly' + ] + }, + excludeMatching: { + items: { + type: 'string' + }, + type: 'array' + }, + includeOnlyMatching: { + items: { + type: 'string' + }, + type: 'array' + } + }, + type: 'object' + }, + schema: false, + schemaPath: '#/items/1/additionalProperties' + } + ], + options: ['always', {excludeOtherStuff: true}] + }, + { + errors: [ + { + data: 'often', + dataPath: '[1].annotateUndefined', + keyword: 'enum', + message: 'should be equal to one of the allowed values', + params: { + allowedValues: [ + 'always', + 'never' + ] + }, + parentSchema: { + enum: [ + 'always', + 'never' + ], + type: 'string' + }, + schema: [ + 'always', + 'never' + ], + schemaPath: '#/items/1/properties/annotateUndefined/enum' + } + ], + options: ['always', {annotateUndefined: 'often'}] + }, + { + errors: [ + { + data: 'everything', + dataPath: '[1].excludeArrowFunctions', + keyword: 'enum', + message: 'should be equal to one of the allowed values', + params: { + allowedValues: [ + false, + true, + 'expressionsOnly' + ] + }, + parentSchema: { + enum: [ + false, + true, + 'expressionsOnly' + ] + }, + schema: [ + false, + true, + 'expressionsOnly' + ], + schemaPath: '#/items/1/properties/excludeArrowFunctions/enum' + } + ], + options: ['always', {excludeArrowFunctions: 'everything'}] + }, + { + errors: [ + { + data: '^foo', + dataPath: '[1].excludeMatching', + keyword: 'type', + message: 'should be array', + params: { + type: 'array' + }, + parentSchema: { + items: { + type: 'string' + }, + type: 'array' + }, + schema: 'array', + schemaPath: '#/items/1/properties/excludeMatching/type' + } + ], + options: ['always', {excludeMatching: '^foo'}] + }, + { + errors: [ + { + data: false, + dataPath: '[1].includeOnlyMatching[0]', + keyword: 'type', + message: 'should be string', + params: { + type: 'string' + }, + parentSchema: { + type: 'string' + }, + schema: 'string', + schemaPath: '#/items/1/properties/includeOnlyMatching/items/type' + } + ], + options: ['always', {includeOnlyMatching: [false]}] + } + ], valid: [ { code: 'return;' diff --git a/tests/rules/assertions/requireValidFileAnnotation.js b/tests/rules/assertions/requireValidFileAnnotation.js index 98c804b9..c28983f5 100644 --- a/tests/rules/assertions/requireValidFileAnnotation.js +++ b/tests/rules/assertions/requireValidFileAnnotation.js @@ -124,6 +124,69 @@ export default { ] } ], + misconfigured: [ + { + errors: [ + { + data: 'sometimes', + dataPath: '[0]', + keyword: 'enum', + message: 'should be equal to one of the allowed values', + params: { + allowedValues: [ + 'always', + 'never' + ] + }, + parentSchema: { + enum: [ + 'always', + 'never' + ], + type: 'string' + }, + schema: [ + 'always', + 'never' + ], + schemaPath: '#/items/0/enum' + } + ], + options: ['sometimes'] + }, + { + errors: [ + { + data: 'upside-down', + dataPath: '[1].annotationStyle', + keyword: 'enum', + message: 'should be equal to one of the allowed values', + params: { + allowedValues: [ + 'none', + 'line', + 'block' + ] + }, + parentSchema: { + enum: [ + 'none', + 'line', + 'block' + ], + type: 'string' + }, + schema: [ + 'none', + 'line', + 'block' + ], + schemaPath: '#/items/1/properties/annotationStyle/enum' + } + ], + options: ['never', {annotationStyle: 'upside-down'}] + } + ], valid: [ { code: 'a;' @@ -178,7 +241,7 @@ export default { }, { code: '// @fixable', - options: [ 'error', 'never' ] + options: ['never'] }, { code: '/* @flow */', diff --git a/tests/rules/assertions/requireVariableType.js b/tests/rules/assertions/requireVariableType.js index 0c208833..09c51c75 100644 --- a/tests/rules/assertions/requireVariableType.js +++ b/tests/rules/assertions/requireVariableType.js @@ -46,6 +46,121 @@ export default { ] } ], + misconfigured: [ + { + errors: [ + { + data: { + excludeOtherStuff: true + }, + dataPath: '[0]', + keyword: 'additionalProperties', + message: 'should NOT have additional properties', + params: { + additionalProperty: 'excludeOtherStuff' + }, + parentSchema: { + additionalProperties: false, + properties: { + excludeVariableMatch: { + type: 'string' + }, + excludeVariableTypes: { + additionalProperties: false, + properties: { + const: { + type: 'boolean' + }, + let: { + type: 'boolean' + }, + var: { + type: 'boolean' + } + }, + type: 'object' + } + }, + type: 'object' + }, + schema: false, + schemaPath: '#/items/0/additionalProperties' + } + ], + options: [{excludeOtherStuff: true}] + }, + { + errors: [ + { + data: 99, + dataPath: '[0].excludeVariableMatch', + keyword: 'type', + message: 'should be string', + params: { + type: 'string' + }, + parentSchema: { + type: 'string' + }, + schema: 'string', + schemaPath: '#/items/0/properties/excludeVariableMatch/type' + } + ], + options: [{excludeVariableMatch: 99}] + }, + { + errors: [ + { + data: { + declare: false + }, + dataPath: '[0].excludeVariableTypes', + keyword: 'additionalProperties', + message: 'should NOT have additional properties', + params: { + additionalProperty: 'declare' + }, + parentSchema: { + additionalProperties: false, + properties: { + const: { + type: 'boolean' + }, + let: { + type: 'boolean' + }, + var: { + type: 'boolean' + } + }, + type: 'object' + }, + schema: false, + schemaPath: '#/items/0/properties/excludeVariableTypes/additionalProperties' + } + ], + options: [{excludeVariableTypes: {declare: false}}] + }, + { + errors: [ + { + data: 'yes', + dataPath: '[0].excludeVariableTypes.let', + keyword: 'type', + message: 'should be boolean', + params: { + type: 'boolean' + }, + parentSchema: { + type: 'boolean' + }, + schema: 'boolean', + schemaPath: '#/items/0/properties/excludeVariableTypes/properties/let/type' + } + ], + options: [{excludeVariableTypes: {let: 'yes'}}] + } + ], valid: [ { code: 'var foo : string = "bar"' diff --git a/tests/rules/assertions/semi.js b/tests/rules/assertions/semi.js index 12b9a789..fcafa9b9 100644 --- a/tests/rules/assertions/semi.js +++ b/tests/rules/assertions/semi.js @@ -31,6 +31,37 @@ export default { output: 'type FooType = {}' } ], + misconfigured: [ + { + errors: [ + { + data: 'temporarily', + dataPath: '[0]', + keyword: 'enum', + message: 'should be equal to one of the allowed values', + params: { + allowedValues: [ + 'always', + 'never' + ] + }, + parentSchema: { + enum: [ + 'always', + 'never' + ], + type: 'string' + }, + schema: [ + 'always', + 'never' + ], + schemaPath: '#/items/0/enum' + } + ], + options: ['temporarily'] + } + ], valid: [ { code: 'type FooType = {};' diff --git a/tests/rules/assertions/sortKeys.js b/tests/rules/assertions/sortKeys.js index ebbdc323..06ec998c 100644 --- a/tests/rules/assertions/sortKeys.js +++ b/tests/rules/assertions/sortKeys.js @@ -43,6 +43,105 @@ export default { options: ['asc', {natural: true}] } ], + misconfigured: [ + { + errors: [ + { + data: 'random', + dataPath: '[0]', + keyword: 'enum', + message: 'should be equal to one of the allowed values', + params: { + allowedValues: [ + 'asc', + 'desc' + ] + }, + parentSchema: { + enum: [ + 'asc', + 'desc' + ], + type: 'string' + }, + schema: [ + 'asc', + 'desc' + ], + schemaPath: '#/items/0/enum' + } + ], + options: ['random'] + }, + { + errors: [ + { + data: { + language: 'jp-JP' + }, + dataPath: '[1]', + keyword: 'additionalProperties', + message: 'should NOT have additional properties', + params: { + additionalProperty: 'language' + }, + parentSchema: { + additionalProperties: false, + properties: { + caseSensitive: { + type: 'boolean' + }, + natural: { + type: 'boolean' + } + }, + type: 'object' + }, + schema: false, + schemaPath: '#/items/1/additionalProperties' + } + ], + options: ['asc', {language: 'jp-JP'}] + }, + { + errors: [ + { + data: 'no', + dataPath: '[1].caseSensitive', + keyword: 'type', + message: 'should be boolean', + params: { + type: 'boolean' + }, + parentSchema: { + type: 'boolean' + }, + schema: 'boolean', + schemaPath: '#/items/1/properties/caseSensitive/type' + } + ], + options: ['desc', {caseSensitive: 'no'}] + }, + { + errors: [ + { + data: 'no', + dataPath: '[1].natural', + keyword: 'type', + message: 'should be boolean', + params: { + type: 'boolean' + }, + parentSchema: { + type: 'boolean' + }, + schema: 'boolean', + schemaPath: '#/items/1/properties/natural/type' + } + ], + options: ['desc', {natural: 'no'}] + } + ], valid: [ { code: 'type FooType = { a: number }' diff --git a/tests/rules/assertions/spaceAfterTypeColon.js b/tests/rules/assertions/spaceAfterTypeColon.js index 4733a60c..9b88e73c 100644 --- a/tests/rules/assertions/spaceAfterTypeColon.js +++ b/tests/rules/assertions/spaceAfterTypeColon.js @@ -1068,7 +1068,86 @@ const ALL = [ TYPE_CAST_EXPRESSIONS ]; +const MISCONFIGURED = [ + { + errors: [ + { + data: 'from time to time', + dataPath: '[0]', + keyword: 'enum', + message: 'should be equal to one of the allowed values', + params: { + allowedValues: [ + 'always', + 'never' + ] + }, + parentSchema: { + enum: [ + 'always', + 'never' + ], + type: 'string' + }, + schema: [ + 'always', + 'never' + ], + schemaPath: '#/items/0/enum' + } + ], + options: ['from time to time'] + }, + { + errors: [ + { + data: { + allowEmoji: true + }, + dataPath: '[1]', + keyword: 'additionalProperties', + message: 'should NOT have additional properties', + params: { + additionalProperty: 'allowEmoji' + }, + parentSchema: { + additionalProperties: false, + properties: { + allowLineBreak: { + type: 'boolean' + } + }, + type: 'object' + }, + schema: false, + schemaPath: '#/items/1/additionalProperties' + } + ], + options: ['always', {allowEmoji: true}] + }, + { + errors: [ + { + data: 'why not?', + dataPath: '[1].allowLineBreak', + keyword: 'type', + message: 'should be boolean', + params: { + type: 'boolean' + }, + parentSchema: { + type: 'boolean' + }, + schema: 'boolean', + schemaPath: '#/items/1/properties/allowLineBreak/type' + } + ], + options: ['always', {allowLineBreak: 'why not?'}] + } +]; + export default { invalid: _.flatMap(ALL, (rules) => { return rules.invalid; }), + misconfigured: MISCONFIGURED, valid: _.flatMap(ALL, (rules) => { return rules.valid; }) }; diff --git a/tests/rules/assertions/spaceBeforeGenericBracket.js b/tests/rules/assertions/spaceBeforeGenericBracket.js index d25bbf28..633a6293 100644 --- a/tests/rules/assertions/spaceBeforeGenericBracket.js +++ b/tests/rules/assertions/spaceBeforeGenericBracket.js @@ -29,6 +29,37 @@ export default { output: 'type X = Promise ' } ], + misconfigured: [ + { + errors: [ + { + data: 'whenever', + dataPath: '[0]', + keyword: 'enum', + message: 'should be equal to one of the allowed values', + params: { + allowedValues: [ + 'always', + 'never' + ] + }, + parentSchema: { + enum: [ + 'always', + 'never' + ], + type: 'string' + }, + schema: [ + 'always', + 'never' + ], + schemaPath: '#/items/0/enum' + } + ], + options: ['whenever'] + } + ], valid: [ { code: 'type X = Promise' diff --git a/tests/rules/assertions/spaceBeforeTypeColon.js b/tests/rules/assertions/spaceBeforeTypeColon.js index 80fe4460..dcefae7b 100644 --- a/tests/rules/assertions/spaceBeforeTypeColon.js +++ b/tests/rules/assertions/spaceBeforeTypeColon.js @@ -882,7 +882,40 @@ const ALL = [ TYPE_CAST_EXPRESSIONS ]; +const MISCONFIGURED = [ + { + errors: [ + { + data: 'wherever', + dataPath: '[0]', + keyword: 'enum', + message: 'should be equal to one of the allowed values', + params: { + allowedValues: [ + 'always', + 'never' + ] + }, + parentSchema: { + enum: [ + 'always', + 'never' + ], + type: 'string' + }, + schema: [ + 'always', + 'never' + ], + schemaPath: '#/items/0/enum' + } + ], + options: ['wherever'] + } +]; + export default { invalid: _.flatMap(ALL, (rules) => { return rules.invalid; }), + misconfigured: MISCONFIGURED, valid: _.flatMap(ALL, (rules) => { return rules.valid; }) }; diff --git a/tests/rules/assertions/typeIdMatch.js b/tests/rules/assertions/typeIdMatch.js index c46dfc87..dd4df27b 100644 --- a/tests/rules/assertions/typeIdMatch.js +++ b/tests/rules/assertions/typeIdMatch.js @@ -20,6 +20,27 @@ export default { ] } ], + misconfigured: [ + { + errors: [ + { + data: 7, + dataPath: '[0]', + keyword: 'type', + message: 'should be string', + params: { + type: 'string' + }, + parentSchema: { + type: 'string' + }, + schema: 'string', + schemaPath: '#/items/0/type' + } + ], + options: [7] + } + ], valid: [ { code: 'type FooType = {};' diff --git a/tests/rules/assertions/unionIntersectionSpacing.js b/tests/rules/assertions/unionIntersectionSpacing.js index d98b2f03..b57864f9 100644 --- a/tests/rules/assertions/unionIntersectionSpacing.js +++ b/tests/rules/assertions/unionIntersectionSpacing.js @@ -212,7 +212,40 @@ const INTERSECTION = { ] }; +const MISCONFIGURED = [ + { + errors: [ + { + data: 'however', + dataPath: '[0]', + keyword: 'enum', + message: 'should be equal to one of the allowed values', + params: { + allowedValues: [ + 'always', + 'never' + ] + }, + parentSchema: { + enum: [ + 'always', + 'never' + ], + type: 'string' + }, + schema: [ + 'always', + 'never' + ], + schemaPath: '#/items/0/enum' + } + ], + options: ['however'] + } +]; + export default { invalid: [...UNION.invalid, ...INTERSECTION.invalid], + misconfigured: MISCONFIGURED, valid: [...UNION.valid, ...INTERSECTION.valid] }; diff --git a/tests/rules/index.js b/tests/rules/index.js index 85eb4514..cb563f8c 100644 --- a/tests/rules/index.js +++ b/tests/rules/index.js @@ -1,9 +1,13 @@ +import assert from 'assert'; import _ from 'lodash'; -import { - RuleTester -} from 'eslint'; +import Ajv from 'ajv'; +import {RuleTester} from 'eslint'; +import rules from 'eslint/lib/rules'; +import validator from 'eslint/lib/config/config-validator'; import plugin from './../../src'; +rules.importPlugin(plugin, 'flowtype'); + const ruleTester = new RuleTester(); const reportingRules = [ @@ -12,9 +16,9 @@ const reportingRules = [ 'delimiter-dangle', 'generic-spacing', 'no-dupe-keys', - 'no-weak-types', 'no-primitive-constructor-types', 'no-types-missing-file-annotation', + 'no-weak-types', 'object-type-delimiter', 'require-parameter-type', 'require-return-type', @@ -23,21 +27,47 @@ const reportingRules = [ 'semi', 'sort-keys', 'space-after-type-colon', - 'space-before-type-colon', 'space-before-generic-bracket', - 'union-intersection-spacing', + 'space-before-type-colon', 'type-id-match', + 'union-intersection-spacing', 'use-flow-type', 'valid-syntax' ]; const parser = require.resolve('babel-eslint'); +const ajv = new Ajv({verbose: true}); for (const ruleName of reportingRules) { /* eslint-disable global-require */ const assertions = require('./assertions/' + _.camelCase(ruleName)); /* eslint-enable global-require */ + if (assertions.misconfigured) { + for (const misconfiguration of assertions.misconfigured) { + RuleTester.describe(ruleName, () => { + RuleTester.describe('misconfigured', () => { + RuleTester.it(JSON.stringify(misconfiguration.options), () => { + const schema = validator.getRuleOptionsSchema('flowtype/' + ruleName); + + if (!schema) { + throw new Error('No schema.'); + } + + const validateSchema = ajv.compile(schema); + + validateSchema(misconfiguration.options); + if (!validateSchema.errors) { + throw new Error('Schema was valid.'); + } + + assert.deepStrictEqual(validateSchema.errors, misconfiguration.errors); + }); + }); + }); + } + } + assertions.invalid = _.map(assertions.invalid, (assertion) => { assertion.parser = parser;