diff --git a/src/index.js b/src/index.js index 26e2fbc1..2a1bdca9 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,7 @@ import _ from 'lodash'; import recommended from './configs/recommended.json'; +import arrayStyleComplexType from './rules/arrayStyleComplexType'; +import arrayStyleSimpleType from './rules/arrayStyleSimpleType'; import booleanStyle from './rules/booleanStyle'; import defineFlowType from './rules/defineFlowType'; import delimiterDangle from './rules/delimiterDangle'; @@ -26,6 +28,8 @@ import validSyntax from './rules/validSyntax'; import {checkFlowFileAnnotation} from './utilities'; const rules = { + 'array-style-complex-type': arrayStyleComplexType, + 'array-style-simple-type': arrayStyleSimpleType, 'boolean-style': booleanStyle, 'define-flow-type': defineFlowType, 'delimiter-dangle': delimiterDangle, diff --git a/src/rules/arrayStyle/index.js b/src/rules/arrayStyle/index.js new file mode 100644 index 00000000..4d54094b --- /dev/null +++ b/src/rules/arrayStyle/index.js @@ -0,0 +1,34 @@ +import isSimpleType from './isSimpleType'; + +const schema = [ + { + enum: ['verbose', 'shorthand'], + type: 'string' + } +]; + +export default (defaultConfig, shorthandHandler, verboseHandler) => { + const create = (context) => { + const verbose = (context.options[0] || defaultConfig) === 'verbose'; + + return { + // shorthand + ArrayTypeAnnotation (node) { + shorthandHandler(isSimpleType(node.elementType), verbose, context, node); + }, + // verbose + GenericTypeAnnotation (node) { + if (node.id.name === 'Array') { + if (node.typeParameters.params.length === 1) { + verboseHandler(isSimpleType(node.typeParameters.params[0]), verbose, context, node); + } + } + } + }; + }; + + return { + create, + schema + }; +}; diff --git a/src/rules/arrayStyle/isSimpleType.js b/src/rules/arrayStyle/isSimpleType.js new file mode 100644 index 00000000..1cbdf2f6 --- /dev/null +++ b/src/rules/arrayStyle/isSimpleType.js @@ -0,0 +1,30 @@ +/** + * Types considered simple: + * + * - primitive types + * - literal types + * - mixed and any types + * - generic types (such as Date, Promise, $Keys, etc.) + * - array type written in shorthand notation + * + * Types not considered simple: + * + * - maybe type + * - function type + * - object type + * - tuple type + * - union and intersection types + * + * Reminder: if you change these semantics, don't forget to modify documentation of `array-style-...` rules + */ + +const simpleTypePatterns = [ + /^(?:Any|Array|Boolean|Generic|Mixed|Number|String|Void)TypeAnnotation$/, + /.+LiteralTypeAnnotation$/ +]; + +export default (node) => { + return simpleTypePatterns.some((pattern) => { + return pattern.test(node.type); + }); +}; diff --git a/src/rules/arrayStyleComplexType.js b/src/rules/arrayStyleComplexType.js new file mode 100644 index 00000000..0cd0e423 --- /dev/null +++ b/src/rules/arrayStyleComplexType.js @@ -0,0 +1,21 @@ +import makeArrayStyleRule from './arrayStyle'; + +const shorthandHandler = (isSimpleType, verbose, context, node) => { + if (!isSimpleType && verbose) { + context.report({ + message: 'Use "Array", not "ComplexType[]"', + node + }); + } +}; + +const verboseHandler = (isSimpleType, verbose, context, node) => { + if (!isSimpleType && !verbose) { + context.report({ + message: 'Use "ComplexType[]", not "Array"', + node + }); + } +}; + +export default makeArrayStyleRule('verbose', shorthandHandler, verboseHandler); diff --git a/src/rules/arrayStyleSimpleType.js b/src/rules/arrayStyleSimpleType.js new file mode 100644 index 00000000..cb1254c6 --- /dev/null +++ b/src/rules/arrayStyleSimpleType.js @@ -0,0 +1,21 @@ +import makeArrayStyleRule from './arrayStyle'; + +const shorthandHandler = (isSimpleType, verbose, context, node) => { + if (isSimpleType && verbose) { + context.report({ + message: 'Use "Array", not "SimpleType[]"', + node + }); + } +}; + +const verboseHandler = (isSimpleType, verbose, context, node) => { + if (isSimpleType && !verbose) { + context.report({ + message: 'Use "SimpleType[]", not "Array"', + node + }); + } +}; + +export default makeArrayStyleRule('shorthand', shorthandHandler, verboseHandler); diff --git a/tests/rules/assertions/arrayStyleComplexType.js b/tests/rules/assertions/arrayStyleComplexType.js new file mode 100644 index 00000000..96fafe05 --- /dev/null +++ b/tests/rules/assertions/arrayStyleComplexType.js @@ -0,0 +1,95 @@ +export default { + invalid: [ + { + code: 'type X = (?string)[]', + errors: [{message: 'Use "Array", not "ComplexType[]"'}] + }, + { + code: 'type X = (?string)[]', + errors: [{message: 'Use "Array", not "ComplexType[]"'}], + options: ['verbose'] + }, + { + code: 'type X = Array', + errors: [{message: 'Use "ComplexType[]", not "Array"'}], + options: ['shorthand'] + }, + { + code: 'type X = (string | number)[]', + errors: [{message: 'Use "Array", not "ComplexType[]"'}] + }, + { + code: 'type X = (string & number)[]', + errors: [{message: 'Use "Array", not "ComplexType[]"'}] + }, + { + code: 'type X = [string, number][]', + errors: [{message: 'Use "Array", not "ComplexType[]"'}] + }, + { + code: 'type X = ({foo: string})[]', + errors: [{message: 'Use "Array", not "ComplexType[]"'}] + }, + { + code: 'type X = (string => number)[]', + errors: [{message: 'Use "Array", not "ComplexType[]"'}] + } + ], + misconfigured: [ + { + errors: [ + { + data: 'normal', + dataPath: '[0]', + keyword: 'enum', + message: 'should be equal to one of the allowed values', + params: { + allowedValues: [ + 'verbose', + 'shorthand' + ] + }, + parentSchema: { + enum: [ + 'verbose', + 'shorthand' + ], + type: 'string' + }, + schema: [ + 'verbose', + 'shorthand' + ], + schemaPath: '#/items/0/enum' + } + ], + options: ['normal'] + } + ], + valid: [ + { + code: 'type X = Array' + }, + { + code: 'type X = Array', + options: ['verbose'] + }, + { + code: 'type X = (?string)[]', + options: ['shorthand'] + }, + { + code: 'type X = Array', + options: ['shorthand'] + }, + { + code: 'type X = Array', + options: ['shorthand'], + settings: { + flowtype: { + onlyFilesWithFlowAnnotation: true + } + } + } + ] +}; diff --git a/tests/rules/assertions/arrayStyleSimpleType.js b/tests/rules/assertions/arrayStyleSimpleType.js new file mode 100644 index 00000000..7c835d7c --- /dev/null +++ b/tests/rules/assertions/arrayStyleSimpleType.js @@ -0,0 +1,110 @@ +export default { + invalid: [ + { + code: 'type X = Array', + errors: [{message: 'Use "SimpleType[]", not "Array"'}] + }, + { + code: 'type X = string[]', + errors: [{message: 'Use "Array", not "SimpleType[]"'}], + options: ['verbose'] + }, + { + code: 'type X = Array', + errors: [{message: 'Use "SimpleType[]", not "Array"'}], + options: ['shorthand'] + }, + { + code: 'type X = Array', + errors: [{message: 'Use "SimpleType[]", not "Array"'}] + }, + { + code: 'type X = Array>', + errors: [{message: 'Use "SimpleType[]", not "Array"'}] + }, + { + code: 'type X = Array<$Keys<{ foo: string }>>', + errors: [{message: 'Use "SimpleType[]", not "Array"'}] + }, + { + code: 'type X = Array', + errors: [{message: 'Use "SimpleType[]", not "Array"'}] + }, + { + code: 'type X = Array', + errors: [{message: 'Use "SimpleType[]", not "Array"'}] + }, + { + code: 'type X = Array', + errors: [{message: 'Use "SimpleType[]", not "Array"'}] + }, + { + code: 'type X = Array', + errors: [{message: 'Use "SimpleType[]", not "Array"'}] + }, + { + code: 'type X = Array', + errors: [{message: 'Use "SimpleType[]", not "Array"'}] + } + ], + misconfigured: [ + { + errors: [ + { + data: 'normal', + dataPath: '[0]', + keyword: 'enum', + message: 'should be equal to one of the allowed values', + params: { + allowedValues: [ + 'verbose', + 'shorthand' + ] + }, + parentSchema: { + enum: [ + 'verbose', + 'shorthand' + ], + type: 'string' + }, + schema: [ + 'verbose', + 'shorthand' + ], + schemaPath: '#/items/0/enum' + } + ], + options: ['normal'] + } + ], + valid: [ + { + code: 'type X = string[]' + }, + { + code: 'type X = Array', + options: ['verbose'] + }, + { + code: 'type X = string[]', + options: ['shorthand'] + }, + { + code: 'type X = string[][]' + }, + { + code: 'type X = (?string)[]', + options: ['verbose'] + }, + { + code: 'type X = string[]', + options: ['verbose'], + settings: { + flowtype: { + onlyFilesWithFlowAnnotation: true + } + } + } + ] +}; diff --git a/tests/rules/index.js b/tests/rules/index.js index 76394305..7e8a418f 100644 --- a/tests/rules/index.js +++ b/tests/rules/index.js @@ -11,6 +11,8 @@ rules.importPlugin(plugin, 'flowtype'); const ruleTester = new RuleTester(); const reportingRules = [ + 'array-style-complex-type', + 'array-style-simple-type', 'boolean-style', 'define-flow-type', 'delimiter-dangle',