diff --git a/.eslintignore b/.eslintignore index fce3c146ab0..49c1ac6eac7 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,7 +2,6 @@ **/staticBundle.js docs/generatedComponentApiDocs.js flow/ -flow-typed/ Libraries/Renderer/* Libraries/vendor/**/* node_modules/ diff --git a/.eslintrc.js b/.eslintrc.js index 4787caf9e4f..1a970847534 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -49,6 +49,8 @@ module.exports = { { files: ['flow-typed/**/*.js'], rules: { + 'lint/valid-flow-typed-signature': 2, + 'no-unused-vars': 0, quotes: 0, }, }, diff --git a/tools/eslint/rules/__tests__/valid-flow-typed-signature-test.js b/tools/eslint/rules/__tests__/valid-flow-typed-signature-test.js new file mode 100644 index 00000000000..8181511bc10 --- /dev/null +++ b/tools/eslint/rules/__tests__/valid-flow-typed-signature-test.js @@ -0,0 +1,48 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +'use strict'; + +const rule = require('../valid-flow-typed-signature.js'); +const {RuleTester} = require('eslint'); + +const ruleTester = new RuleTester({ + parser: require.resolve('hermes-eslint'), + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + }, +}); + +ruleTester.run('valid-flow-typed-signature', rule, { + valid: [ + { + code: `// ...`, + }, + { + code: [ + `// flow-typed signature: 9333721862f426d869c32ea6f7cecfda`, + `// flow-typed version: 123456/xyz_v1.x.x/flow_>=v0.83.x`, + ``, + `declare module "xyz" {}`, + ].join('\n'), + }, + ], + invalid: [ + { + code: [ + `// flow-typed signature: 90affbd9a1954ec9ff029b7ad7183a16`, + `// flow-typed version: 123456/xyz_v1.x.x/flow_>=v0.83.x`, + ``, + `declare module "xyz" {}`, + ].join('\n'), + errors: [{messageId: 'invalidSignature'}], + }, + ], +}); diff --git a/tools/eslint/rules/valid-flow-typed-signature.js b/tools/eslint/rules/valid-flow-typed-signature.js new file mode 100644 index 00000000000..c90ed5bbef6 --- /dev/null +++ b/tools/eslint/rules/valid-flow-typed-signature.js @@ -0,0 +1,71 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +'use strict'; + +const crypto = require('node:crypto'); + +// @see https://github.com/flow-typed/flow-typed +const HASH_COMMENT_RE = /\/\/ flow-typed signature: (.*)$/; + +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'Require valid flow-typed signatures', + recommended: true, + }, + messages: { + invalidSignature: + 'Invalid flow-typed signature; avoid local modifications', + }, + schema: [], + }, + + create(context) { + return { + Program() { + const sourceText = context.getSourceCode().getText(); + + const firstLineEndIndex = sourceText.indexOf('\n'); + const firstLine = sourceText.substr(0, firstLineEndIndex); + + const match = firstLine.match(HASH_COMMENT_RE); + if (match == null) { + // Not a signed flow-typed definition file. + return; + } + + const hash = match[1]; + const versionedCode = sourceText.substr(firstLineEndIndex + 1); + if (md5(versionedCode) === hash) { + return; + } + + context.report({ + loc: { + start: { + line: 1, + column: firstLine.length - hash.length, + }, + end: { + line: 1, + column: firstLine.length, + }, + }, + messageId: 'invalidSignature', + }); + }, + }; + }, +}; + +function md5(string) { + return crypto.createHash('md5').update(string).digest('hex'); +}