Skip to content

Commit

Permalink
✨ add es/no-optional-chaining rule (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi authored Nov 18, 2020
1 parent f2b6c19 commit b112d4d
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ There are multiple configs which enable all rules in this category: `plugin:es/n
| [es/no-global-this](./no-global-this.md) | disallow the `globalThis` variable. | |
| [es/no-import-meta](./no-import-meta.md) | disallow `import.meta` meta property. | |
| [es/no-nullish-coalescing-operators](./no-nullish-coalescing-operators.md) | disallow nullish coalescing operators. | |
| [es/no-optional-chaining](./no-optional-chaining.md) | disallow optional chaining. | |
| [es/no-promise-all-settled](./no-promise-all-settled.md) | disallow `Promise.allSettled` function. | |

## ES2019
Expand Down
28 changes: 28 additions & 0 deletions docs/rules/no-optional-chaining.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# disallow optional chaining (es/no-optional-chaining)

This rule reports ES2020 [Optional Chaining](https://github.com/tc39/proposal-optional-chaining) as errors.

## Examples

⛔ Examples of **incorrect** code for this rule:

<eslint-playground type="bad" code="/*eslint es/no-optional-chaining: error */
var x = a?.b
var x = a?.[b]
foo?.()
" />

👌 Examples of **correct** code for this rule:

<eslint-playground type="good" code="/*eslint es/no-optional-chaining: error */
var x = a != null ? a.b : undefined
var x = a && a.b
var x = a != null ? a[b] : undefined
var x = a && a[b]
foo && foo()
" />

## 📚 References

- [Rule source](https://github.com/mysticatea/eslint-plugin-es/blob/v3.0.1/lib/rules/no-optional-chaining.js)
- [Test source](https://github.com/mysticatea/eslint-plugin-es/blob/v3.0.1/tests/lib/rules/no-optional-chaining.js)
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ module.exports = {
"no-object-values": require("./rules/no-object-values"),
"no-octal-numeric-literals": require("./rules/no-octal-numeric-literals"),
"no-optional-catch-binding": require("./rules/no-optional-catch-binding"),
"no-optional-chaining": require("./rules/no-optional-chaining"),
"no-promise": require("./rules/no-promise"),
"no-promise-all-settled": require("./rules/no-promise-all-settled"),
"no-property-shorthands": require("./rules/no-property-shorthands"),
Expand Down
61 changes: 61 additions & 0 deletions lib/rules/no-optional-chaining.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* @author Yosuke Ota <https://github.com/ota-meshi>
* See LICENSE file in root directory for full license.
*/
"use strict"

module.exports = {
meta: {
docs: {
description: "disallow optional chaining.",
category: "ES2020",
recommended: false,
url:
"http://mysticatea.github.io/eslint-plugin-es/rules/no-optional-chaining.html",
},
fixable: null,
messages: {
forbidden: "ES2020 optional chaining is forbidden.",
},
schema: [],
type: "problem",
},
create(context) {
const sourceCode = context.getSourceCode()

/**
* Checks if the given token is a `?.` token or not.
* @param {Token} token The token to check.
* @returns {boolean} `true` if the token is a `?.` token.
*/
function isQuestionDotToken(token) {
return (
token.value === "?." &&
(token.type === "Punctuator" || // espree has been parsed well.
// espree@7.1.0 doesn't parse "?." tokens well. Therefore, get the string from the source code and check it.
sourceCode.getText(token) === "?.")
)
}

return {
"CallExpression[optional=true]"(node) {
context.report({
node: sourceCode.getTokenAfter(
node.callee,
isQuestionDotToken
),
messageId: "forbidden",
})
},
"MemberExpression[optional=true]"(node) {
context.report({
node: sourceCode.getTokenAfter(
node.object,
isQuestionDotToken
),
messageId: "forbidden",
})
},
}
},
}
90 changes: 90 additions & 0 deletions tests/lib/rules/no-optional-chaining.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* @author Yosuke Ota <https://github.com/ota-meshi>
* See LICENSE file in root directory for full license.
*/
"use strict"

const RuleTester = require("../../tester")
const rule = require("../../../lib/rules/no-optional-chaining.js")

if (!RuleTester.isSupported(2020)) {
//eslint-disable-next-line no-console
console.log("Skip the tests of no-optional-chaining.")
return
}

new RuleTester().run("no-optional-chaining", rule, {
valid: ["var x = a.b", "var x = a[b]", "foo()"],
invalid: [
{
code: "var x = a?.b",
errors: [
{
message: "ES2020 optional chaining is forbidden.",
column: 10,
endColumn: 12,
},
],
},
{
code: "var x = a?.[b]",
errors: [
{
message: "ES2020 optional chaining is forbidden.",
column: 10,
endColumn: 12,
},
],
},
{
code: "foo?.()",
errors: [
{
message: "ES2020 optional chaining is forbidden.",
column: 4,
endColumn: 6,
},
],
},
{
code: "var x = ((a?.b)?.c)?.()",
errors: [
{
message: "ES2020 optional chaining is forbidden.",
column: 12,
endColumn: 14,
},
{
message: "ES2020 optional chaining is forbidden.",
column: 16,
endColumn: 18,
},
{
message: "ES2020 optional chaining is forbidden.",
column: 20,
endColumn: 22,
},
],
},
{
code: "var x = a/*?.*/?.b",
errors: [
{
message: "ES2020 optional chaining is forbidden.",
column: 16,
endColumn: 18,
},
],
},
{
code: "var x = '?.'?.['?.']",
errors: [
{
message: "ES2020 optional chaining is forbidden.",
column: 13,
endColumn: 15,
},
],
},
],
})

0 comments on commit b112d4d

Please sign in to comment.