From 4e191b762afd037268ab6b9a61fc57e8fc953f80 Mon Sep 17 00:00:00 2001 From: micaste Date: Tue, 18 Apr 2017 19:15:58 +0200 Subject: [PATCH] feat: add no-types-missing-flow-annotation (#222) * feat: disallow types in files missing a file annotation * chore: add doc entry * fix: ignore flow files * fix: renamed to avoid flow auto include the file in some setups * chore: clean up, add valid test cases * feat: Allow types in files with an @noflow annotation. --- .../rules/no-types-missing-flow-annotation.md | 13 +++++ src/index.js | 8 ++- src/rules/noTypesMissingFileAnnotation.js | 39 +++++++++++++ src/utilities/isFlowFile.js | 13 ++++- .../noTypesMissingFileAnnotation.js | 57 +++++++++++++++++++ tests/rules/index.js | 1 + 6 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 .README/rules/no-types-missing-flow-annotation.md create mode 100644 src/rules/noTypesMissingFileAnnotation.js create mode 100644 tests/rules/assertions/noTypesMissingFileAnnotation.js diff --git a/.README/rules/no-types-missing-flow-annotation.md b/.README/rules/no-types-missing-flow-annotation.md new file mode 100644 index 00000000..799ef000 --- /dev/null +++ b/.README/rules/no-types-missing-flow-annotation.md @@ -0,0 +1,13 @@ +### `no-types-missing-file-annotation` + +Disallows Flow type imports, aliases, and annotations in files missing a valid Flow file declaration (or a @noflow annotation). + +```js +{ + "rules": { + "flowtype/no-types-missing-file-annotation": 2 + } +} +``` + + diff --git a/src/index.js b/src/index.js index 304f2e2c..a5483a49 100644 --- a/src/index.js +++ b/src/index.js @@ -3,6 +3,7 @@ import {checkFlowFileAnnotation} from './utilities'; import defineFlowType from './rules/defineFlowType'; import genericSpacing from './rules/genericSpacing'; import noWeakTypes from './rules/noWeakTypes'; +import noTypesMissingFileAnnotation from './rules/noTypesMissingFileAnnotation'; import requireParameterType from './rules/requireParameterType'; import requireReturnType from './rules/requireReturnType'; import requireValidFileAnnotation from './rules/requireValidFileAnnotation'; @@ -30,6 +31,7 @@ const rules = { 'generic-spacing': genericSpacing, 'no-dupe-keys': noDupeKeys, 'no-primitive-constructor-types': noPrimitiveConstructorTypes, + 'no-types-missing-file-annotation': noTypesMissingFileAnnotation, 'no-weak-types': noWeakTypes, 'object-type-delimiter': objectTypeDelimiter, 'require-parameter-type': requireParameterType, @@ -51,7 +53,7 @@ export default { configs: { recommended }, - rules: _.mapValues(rules, (rule) => { + rules: _.mapValues(rules, (rule, key) => { // Support current and deprecated rule formats if (_.isPlainObject(rule)) { return { @@ -60,6 +62,10 @@ export default { }; } + if (key === 'no-types-missing-file-annotation') { + return rule; + } + return _.partial(checkFlowFileAnnotation, rule); }), rulesConfig: { diff --git a/src/rules/noTypesMissingFileAnnotation.js b/src/rules/noTypesMissingFileAnnotation.js new file mode 100644 index 00000000..cff6e1e1 --- /dev/null +++ b/src/rules/noTypesMissingFileAnnotation.js @@ -0,0 +1,39 @@ +import {isFlowFile} from '../utilities'; + +/** + * Disallows the use for flow types without a valid file annotation. + * Only checks files without a valid flow annotation. + */ + +export default (context) => { + // Skip flow files + if (isFlowFile(context, false)) { + return {}; + } + + const reporter = (node, type) => { + context.report({ + data: {type}, + message: 'Type {{type}} require valid Flow declaration.', + node + }); + }; + + return { + ImportDeclaration (node) { + if (node.importKind === 'type') { + reporter(node, 'imports'); + } + if (node.importKind === 'value' && + node.specifiers.some((specifier) => { return specifier.importKind === 'type'; })) { + reporter(node, 'imports'); + } + }, + TypeAlias (node) { + reporter(node, 'aliases'); + }, + TypeAnnotation (node) { + reporter(node, 'annotations'); + } + }; +}; diff --git a/src/utilities/isFlowFile.js b/src/utilities/isFlowFile.js index 4e157c7f..1cbb4cfd 100644 --- a/src/utilities/isFlowFile.js +++ b/src/utilities/isFlowFile.js @@ -1,6 +1,13 @@ import isFlowFileAnnotation from './isFlowFileAnnotation.js'; - -export default (context) => { +/* eslint-disable flowtype/require-valid-file-annotation */ +/** + * Checks whether a file has an @flow or @noflow annotation. + * @param context + * @param [strict] - By default, the function returns true if the file starts with @flow but not if it + * starts by @noflow. When the strict flag is set to false, the function returns true if the flag has @noflow also. + */ +/* eslint-enable flowtype/require-valid-file-annotation */ +export default (context, strict = true) => { const comments = context.getAllComments(); if (!comments.length) { @@ -9,5 +16,5 @@ export default (context) => { const firstComment = comments[0]; - return isFlowFileAnnotation(firstComment.value) && !/no/.test(firstComment.value); + return isFlowFileAnnotation(firstComment.value) && !(strict && /no/.test(firstComment.value)); }; diff --git a/tests/rules/assertions/noTypesMissingFileAnnotation.js b/tests/rules/assertions/noTypesMissingFileAnnotation.js new file mode 100644 index 00000000..24f31838 --- /dev/null +++ b/tests/rules/assertions/noTypesMissingFileAnnotation.js @@ -0,0 +1,57 @@ +export default { + invalid: [ + { + code: 'const x: number = 42;', + errors: [{ + message: 'Type annotations require valid Flow declaration.' + }] + }, + { + code: 'type FooType = number;', + errors: [{ + message: 'Type aliases require valid Flow declaration.' + }] + }, + { + code: 'import type A from "a"', + errors: [{ + message: 'Type imports require valid Flow declaration.' + }] + }, + { + code: 'import type {A} from "a"', + errors: [{ + message: 'Type imports require valid Flow declaration.' + }] + }, + { + code: 'import {type A} from "a"', + errors: [{ + message: 'Type imports require valid Flow declaration.' + }] + }, + { + code: 'function t(): T{}', + errors: [{ + message: 'Type annotations require valid Flow declaration.' + }] + } + ], + valid: [ + { + code: '// @flow\nconst x: number = 42;' + }, + { + code: '/* @flow weak */\ntype FooType = number;' + }, + { + code: '/* @noflow */\ntype FooType = number;' + }, + { + code: '/* @noflow */\nimport type A from "a"' + }, + { + code: '/* @noflow */\nimport {type A} from "a"' + } + ] +}; diff --git a/tests/rules/index.js b/tests/rules/index.js index df3f47d0..85eb4514 100644 --- a/tests/rules/index.js +++ b/tests/rules/index.js @@ -14,6 +14,7 @@ const reportingRules = [ 'no-dupe-keys', 'no-weak-types', 'no-primitive-constructor-types', + 'no-types-missing-file-annotation', 'object-type-delimiter', 'require-parameter-type', 'require-return-type',