-
Notifications
You must be signed in to change notification settings - Fork 24.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move error-subclass-name lint rule to GitHub
Summary: Ports an internal ESLint rule used at Facebook, `error-subclass-name`, to cover the React Native codebase. This rule enforces that error classes ( = those with PascalCase names ending with `Error`) only extend other error classes, and that regular functions don't have names that could be mistaken for those of error classes. Reviewed By: rubennorte Differential Revision: D17829298 fbshipit-source-id: 834e457343034a0897ab394b6a2d941789953d2e
- Loading branch information
1 parent
1dc03f4
commit 6611c4b
Showing
9 changed files
with
228 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
load("@fbsource//tools/build_defs/third_party:yarn_defs.bzl", "yarn_workspace") | ||
|
||
yarn_workspace( | ||
name = "yarn-workspace", | ||
srcs = glob( | ||
["**/*.js"], | ||
exclude = [ | ||
"**/__fixtures__/**", | ||
"**/__flowtests__/**", | ||
"**/__mocks__/**", | ||
"**/__server_snapshot_tests__/**", | ||
"**/__tests__/**", | ||
"**/node_modules/**", | ||
"**/node_modules/.bin/**", | ||
"**/.*", | ||
"**/.*/**", | ||
"**/.*/.*", | ||
"**/*.xcodeproj/**", | ||
"**/*.xcworkspace/**", | ||
], | ||
), | ||
visibility = ["PUBLIC"], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
100 changes: 100 additions & 0 deletions
100
packages/eslint-plugin-react-native-community/__tests__/error-subclass-name-test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @emails oncall+react_native | ||
* @format | ||
*/ | ||
|
||
'use strict'; | ||
|
||
const ESLintTester = require('./eslint-tester.js'); | ||
|
||
const rule = require('../error-subclass-name.js'); | ||
|
||
const eslintTester = new ESLintTester(); | ||
|
||
const INVALID_SUPERCLASS_MESSAGE = | ||
"'SomethingEndingWithError' must extend an error class (like 'Error') because its name is in PascalCase and ends with 'Error'."; | ||
const INVALID_OWN_NAME_MESSAGE = | ||
"'Foo' may not be the name of an error class. It should be in PascalCase and end with 'Error'."; | ||
const MISSING_OWN_NAME_MESSAGE = | ||
"An error class should have a PascalCase name ending with 'Error'."; | ||
const INVALID_FUNCTION_NAME_MESSAGE = | ||
"'SomethingEndingWithError' is a reserved name. PascalCase names ending with 'Error' are reserved for error classes and may not be used for regular functions. Either rename this function or convert it to a class that extends 'Error'."; | ||
|
||
eslintTester.run('../error-subclass-name', rule, { | ||
valid: [ | ||
'class FooError extends Error {}', | ||
'(class FooError extends Error {})', | ||
'class FooError extends SomethingEndingWithError {}', | ||
'(class FooError extends SomethingEndingWithError {})', | ||
'function makeError() {}', | ||
'(function () {})', | ||
|
||
// The following cases are currently allowed but could be disallowed in the | ||
// future. This is technically an escape hatch. | ||
'class Foo extends SomeLibrary.FooError {}', | ||
'(class extends SomeLibrary.FooError {})', | ||
], | ||
invalid: [ | ||
{ | ||
code: 'class SomethingEndingWithError {}', | ||
errors: [{message: INVALID_SUPERCLASS_MESSAGE}], | ||
}, | ||
{ | ||
code: '(class SomethingEndingWithError {})', | ||
errors: [{message: INVALID_SUPERCLASS_MESSAGE}], | ||
}, | ||
{ | ||
code: 'class Foo extends Error {}', | ||
errors: [{message: INVALID_OWN_NAME_MESSAGE}], | ||
}, | ||
{ | ||
code: '(class Foo extends Error {})', | ||
errors: [{message: INVALID_OWN_NAME_MESSAGE}], | ||
}, | ||
{ | ||
code: 'class Foo extends SomethingEndingWithError {}', | ||
errors: [{message: INVALID_OWN_NAME_MESSAGE}], | ||
}, | ||
{ | ||
code: '(class Foo extends SomethingEndingWithError {})', | ||
errors: [{message: INVALID_OWN_NAME_MESSAGE}], | ||
}, | ||
{ | ||
code: '(class extends Error {})', | ||
errors: [{message: MISSING_OWN_NAME_MESSAGE}], | ||
}, | ||
{ | ||
code: 'class SomethingEndingWithError extends C {}', | ||
errors: [{message: INVALID_SUPERCLASS_MESSAGE}], | ||
}, | ||
{ | ||
code: '(class SomethingEndingWithError extends C {})', | ||
errors: [{message: INVALID_SUPERCLASS_MESSAGE}], | ||
}, | ||
{ | ||
code: 'function SomethingEndingWithError() {}', | ||
errors: [{message: INVALID_FUNCTION_NAME_MESSAGE}], | ||
}, | ||
{ | ||
code: '(function SomethingEndingWithError() {})', | ||
errors: [{message: INVALID_FUNCTION_NAME_MESSAGE}], | ||
}, | ||
|
||
// The following cases are intentionally disallowed because the member | ||
// expression `SomeLibrary.FooError` doesn't imply that the superclass is | ||
// actually declared with the name `FooError`. | ||
{ | ||
code: 'class SomethingEndingWithError extends SomeLibrary.FooError {}', | ||
errors: [{message: INVALID_SUPERCLASS_MESSAGE}], | ||
}, | ||
{ | ||
code: '(class SomethingEndingWithError extends SomeLibrary.FooError {})', | ||
errors: [{message: INVALID_SUPERCLASS_MESSAGE}], | ||
}, | ||
], | ||
}); |
22 changes: 22 additions & 0 deletions
22
packages/eslint-plugin-react-native-community/__tests__/eslint-tester.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its 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 ESLintTester = require('eslint').RuleTester; | ||
|
||
ESLintTester.setDefaultConfig({ | ||
parser: 'babel-eslint', | ||
parserOptions: { | ||
ecmaVersion: 6, | ||
sourceType: 'module', | ||
}, | ||
}); | ||
|
||
module.exports = ESLintTester; |
66 changes: 66 additions & 0 deletions
66
packages/eslint-plugin-react-native-community/error-subclass-name.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its 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'; | ||
|
||
module.exports = function rule(context) { | ||
function classVisitor(node) { | ||
const {superClass, id} = node; | ||
const nodeIsError = isErrorLikeId(id); | ||
const superIsError = isErrorLikeId(superClass); | ||
if (nodeIsError && !superIsError) { | ||
const idName = getNameFromId(id); | ||
context.report({ | ||
node: superClass || id, | ||
message: `'${idName}' must extend an error class (like 'Error') because its name is in PascalCase and ends with 'Error'.`, | ||
}); | ||
} else if (superIsError && !nodeIsError) { | ||
const idName = getNameFromId(id); | ||
context.report({ | ||
node: id || node, | ||
message: idName | ||
? `'${idName}' may not be the name of an error class. It should be in PascalCase and end with 'Error'.` | ||
: "An error class should have a PascalCase name ending with 'Error'.", | ||
}); | ||
} | ||
} | ||
|
||
function functionVisitor(node) { | ||
const {id} = node; | ||
const nodeIsError = isErrorLikeId(id); | ||
if (nodeIsError) { | ||
const idName = getNameFromId(id); | ||
context.report({ | ||
node: id, | ||
message: `'${idName}' is a reserved name. PascalCase names ending with 'Error' are reserved for error classes and may not be used for regular functions. Either rename this function or convert it to a class that extends 'Error'.`, | ||
}); | ||
} | ||
} | ||
|
||
return { | ||
ClassDeclaration: classVisitor, | ||
ClassExpression: classVisitor, | ||
FunctionExpression: functionVisitor, | ||
FunctionDeclaration: functionVisitor, | ||
}; | ||
}; | ||
|
||
// Checks whether `node` is an identifier (or similar name node) with a | ||
// PascalCase name ending with 'Error'. | ||
function isErrorLikeId(node) { | ||
return ( | ||
node && node.type === 'Identifier' && /^([A-Z].*)?Error$/.test(node.name) | ||
); | ||
} | ||
|
||
// If `node` is an identifier (or similar name node), returns its name as a | ||
// string. Otherwise returns null. | ||
function getNameFromId(node) { | ||
return node ? node.name : null; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters