From afd42108d1aa2289dc474bc1611f8851cdb2313a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Nevyho=C5=A1t=C4=9Bn=C3=BD?= Date: Fri, 29 Sep 2017 18:45:51 +0200 Subject: [PATCH] feat: Implement array style rules This commit adds two new rules: array-style-complex-type and array-style-simple-type. In Flow, there are two array notation styles, verbose (Array) and shorthand (Type[]). These rules enforce consistent notation. Array element types are also divided into two categories: complex and simple. This is useful for e.g. using shorthand notation for simple types and and verbose notation for complex types. What "simple" and "complex" means is explained in rules' documentation. --- src/index.js | 4 + src/rules/arrayStyle/index.js | 34 ++++++ src/rules/arrayStyle/isSimpleType.js | 30 +++++ src/rules/arrayStyleComplexType.js | 21 ++++ src/rules/arrayStyleSimpleType.js | 21 ++++ .../rules/assertions/arrayStyleComplexType.js | 95 +++++++++++++++ .../rules/assertions/arrayStyleSimpleType.js | 110 ++++++++++++++++++ tests/rules/index.js | 2 + 8 files changed, 317 insertions(+) create mode 100644 src/rules/arrayStyle/index.js create mode 100644 src/rules/arrayStyle/isSimpleType.js create mode 100644 src/rules/arrayStyleComplexType.js create mode 100644 src/rules/arrayStyleSimpleType.js create mode 100644 tests/rules/assertions/arrayStyleComplexType.js create mode 100644 tests/rules/assertions/arrayStyleSimpleType.js 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',