diff --git a/docs/configs/index.md b/docs/configs/index.md index 0ab22bbf..f8fcc355 100644 --- a/docs/configs/index.md +++ b/docs/configs/index.md @@ -1029,6 +1029,34 @@ export default [ } ``` +## no-explicit-resource-management + +disallow proposal ES2026 [Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management)\ +⚠️ This config will be changed in the minor versions of this plugin. + +This configs includes rules for [es-x/no-asyncdisposablestack](../rules/no-asyncdisposablestack.md), [es-x/no-disposablestack](../rules/no-disposablestack.md), [es-x/no-suppressederror](../rules/no-suppressederror.md), and [es-x/no-using-declarations](../rules/no-using-declarations.md). + +### [Config (Flat Config)] + +eslint.config.js: + +```js +import pluginESx from "eslint-plugin-es-x" +export default [ + pluginESx.configs['flat/no-explicit-resource-management'] +] +``` + +### [Legacy Config] + +.eslintrc.*: + +```json +{ + "extends": ["plugin:es-x/no-explicit-resource-management"], +} +``` + ## no-float16array disallow proposal ES2025 [Float16Array](https://github.com/tc39/proposal-float16array)\ diff --git a/docs/rules/index.md b/docs/rules/index.md index 6ed80c73..e66db543 100644 --- a/docs/rules/index.md +++ b/docs/rules/index.md @@ -11,7 +11,11 @@ There is a config that enables the rules in this category: [`no-new-in-esnext`] | Rule ID | Description | | |:--------|:------------|:--:| | [es-x/no-array-fromasync](./no-array-fromasync.md) | disallow the `Array.fromAsync` method. | | +| [es-x/no-asyncdisposablestack](./no-asyncdisposablestack.md) | disallow the `AsyncDisposableStack` class. | | +| [es-x/no-disposablestack](./no-disposablestack.md) | disallow the `DisposableStack` class. | | | [es-x/no-error-iserror](./no-error-iserror.md) | disallow the `Error.isError` method. | | +| [es-x/no-suppressederror](./no-suppressederror.md) | disallow the `SuppressedError` class. | | +| [es-x/no-using-declarations](./no-using-declarations.md) | disallow `using` and `await using` declarations. | | ## ES2025 @@ -409,6 +413,8 @@ Rules in this category are not included in any preset. | [es-x/no-nonstandard-array-prototype-properties](./no-nonstandard-array-prototype-properties.md) | disallow non-standard properties on Array instance. | | | [es-x/no-nonstandard-arraybuffer-properties](./no-nonstandard-arraybuffer-properties.md) | disallow non-standard static properties on `ArrayBuffer` class. | | | [es-x/no-nonstandard-arraybuffer-prototype-properties](./no-nonstandard-arraybuffer-prototype-properties.md) | disallow non-standard properties on ArrayBuffer instance. | | +| [es-x/no-nonstandard-asyncdisposablestack-properties](./no-nonstandard-asyncdisposablestack-properties.md) | disallow non-standard static properties on `AsyncDisposableStack` class. | | +| [es-x/no-nonstandard-asyncdisposablestack-prototype-properties](./no-nonstandard-asyncdisposablestack-prototype-properties.md) | disallow non-standard properties on AsyncDisposableStack instance. | | | [es-x/no-nonstandard-atomics-properties](./no-nonstandard-atomics-properties.md) | disallow non-standard static properties on `Atomics`. | | | [es-x/no-nonstandard-bigint-properties](./no-nonstandard-bigint-properties.md) | disallow non-standard static properties on `BigInt` class. | | | [es-x/no-nonstandard-bigint-prototype-properties](./no-nonstandard-bigint-prototype-properties.md) | disallow non-standard properties on BigInt instance. | | @@ -418,6 +424,8 @@ Rules in this category are not included in any preset. | [es-x/no-nonstandard-dataview-prototype-properties](./no-nonstandard-dataview-prototype-properties.md) | disallow non-standard properties on DataView instance. | | | [es-x/no-nonstandard-date-properties](./no-nonstandard-date-properties.md) | disallow non-standard static properties on `Date` class. | | | [es-x/no-nonstandard-date-prototype-properties](./no-nonstandard-date-prototype-properties.md) | disallow non-standard properties on Date instance. | | +| [es-x/no-nonstandard-disposablestack-properties](./no-nonstandard-disposablestack-properties.md) | disallow non-standard static properties on `DisposableStack` class. | | +| [es-x/no-nonstandard-disposablestack-prototype-properties](./no-nonstandard-disposablestack-prototype-properties.md) | disallow non-standard properties on DisposableStack instance. | | | [es-x/no-nonstandard-error-properties](./no-nonstandard-error-properties.md) | disallow non-standard static properties on `Error` class. | | | [es-x/no-nonstandard-finalizationregistry-properties](./no-nonstandard-finalizationregistry-properties.md) | disallow non-standard static properties on `FinalizationRegistry` class. | | | [es-x/no-nonstandard-finalizationregistry-prototype-properties](./no-nonstandard-finalizationregistry-prototype-properties.md) | disallow non-standard properties on FinalizationRegistry instance. | | diff --git a/docs/rules/no-asyncdisposablestack.md b/docs/rules/no-asyncdisposablestack.md new file mode 100644 index 00000000..60e9d93f --- /dev/null +++ b/docs/rules/no-asyncdisposablestack.md @@ -0,0 +1,33 @@ +--- +title: "es-x/no-asyncdisposablestack" +description: "disallow the `AsyncDisposableStack` class" +--- + +# es-x/no-asyncdisposablestack +> disallow the `AsyncDisposableStack` class + +- ❗ ***This rule has not been released yet.*** +- ✅ The following configurations enable this rule: [no-explicit-resource-management] and [no-new-in-esnext] + +This rule reports ES2026 [`AsyncDisposableStack` class](https://github.com/tc39/proposal-explicit-resource-management) as errors. + +## 💡 Examples + +⛔ Examples of **incorrect** code for this rule: + + + +```js +/*eslint es-x/no-asyncdisposablestack: error */ +let asyncdisposablestack = new AsyncDisposableStack() +``` + + + +## 📚 References + +- [Rule source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/lib/rules/no-asyncdisposablestack.js) +- [Test source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/tests/lib/rules/no-asyncdisposablestack.js) + +[no-explicit-resource-management]: ../configs/index.md#no-explicit-resource-management +[no-new-in-esnext]: ../configs/index.md#no-new-in-esnext diff --git a/docs/rules/no-disposablestack.md b/docs/rules/no-disposablestack.md new file mode 100644 index 00000000..3a048d0a --- /dev/null +++ b/docs/rules/no-disposablestack.md @@ -0,0 +1,33 @@ +--- +title: "es-x/no-disposablestack" +description: "disallow the `DisposableStack` class" +--- + +# es-x/no-disposablestack +> disallow the `DisposableStack` class + +- ❗ ***This rule has not been released yet.*** +- ✅ The following configurations enable this rule: [no-explicit-resource-management] and [no-new-in-esnext] + +This rule reports ES2026 [`DisposableStack` class](https://github.com/tc39/proposal-explicit-resource-management) as errors. + +## 💡 Examples + +⛔ Examples of **incorrect** code for this rule: + + + +```js +/*eslint es-x/no-disposablestack: error */ +let disposablestack = new DisposableStack() +``` + + + +## 📚 References + +- [Rule source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/lib/rules/no-disposablestack.js) +- [Test source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/tests/lib/rules/no-disposablestack.js) + +[no-explicit-resource-management]: ../configs/index.md#no-explicit-resource-management +[no-new-in-esnext]: ../configs/index.md#no-new-in-esnext diff --git a/docs/rules/no-nonstandard-asyncdisposablestack-properties.md b/docs/rules/no-nonstandard-asyncdisposablestack-properties.md new file mode 100644 index 00000000..2f4bdb09 --- /dev/null +++ b/docs/rules/no-nonstandard-asyncdisposablestack-properties.md @@ -0,0 +1,56 @@ +--- +title: "es-x/no-nonstandard-asyncdisposablestack-properties" +description: "disallow non-standard static properties on `AsyncDisposableStack` class" +--- + +# es-x/no-nonstandard-asyncdisposablestack-properties +> disallow non-standard static properties on `AsyncDisposableStack` class + +- ❗ ***This rule has not been released yet.*** + +This rule reports non-standard static properties on `AsyncDisposableStack` class as errors. + +## 💡 Examples + +⛔ Examples of **incorrect** code for this rule: + + + +```js +/*eslint es-x/no-nonstandard-asyncdisposablestack-properties: error */ +AsyncDisposableStack.unknown(); +``` + + + +## 🔧 Options + +This rule has an option. + +```jsonc +{ + "rules": { + "es-x/no-nonstandard-asyncdisposablestack-properties": [ + "error", + { + "allow": [], + "allowTestedProperty": false + } + ] + } +} +``` + +### allow: string[] + +An array of non-standard property names to allow. + +### allowTestedProperty: boolean + +Configure the allowTestedProperty mode for only this rule. +This is prior to the `settings['es-x'].allowTestedProperty` setting. + +## 📚 References + +- [Rule source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/lib/rules/no-nonstandard-asyncdisposablestack-properties.js) +- [Test source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/tests/lib/rules/no-nonstandard-asyncdisposablestack-properties.js) diff --git a/docs/rules/no-nonstandard-asyncdisposablestack-prototype-properties.md b/docs/rules/no-nonstandard-asyncdisposablestack-prototype-properties.md new file mode 100644 index 00000000..d8767688 --- /dev/null +++ b/docs/rules/no-nonstandard-asyncdisposablestack-prototype-properties.md @@ -0,0 +1,57 @@ +--- +title: "es-x/no-nonstandard-asyncdisposablestack-prototype-properties" +description: "disallow non-standard properties on AsyncDisposableStack instance" +--- + +# es-x/no-nonstandard-asyncdisposablestack-prototype-properties +> disallow non-standard properties on AsyncDisposableStack instance + +- ❗ ***This rule has not been released yet.*** + +This rule reports non-standard properties on AsyncDisposableStack instance as errors. + +## 💡 Examples + +⛔ Examples of **incorrect** code for this rule: + + + +```js +/*eslint es-x/no-nonstandard-asyncdisposablestack-prototype-properties: error */ +const foo = new AsyncDisposableStack(); +foo.unknown(); +``` + + + +## 🔧 Options + +This rule has an option. + +```jsonc +{ + "rules": { + "es-x/no-nonstandard-asyncdisposablestack-prototype-properties": [ + "error", + { + "allow": [], + "allowTestedProperty": false + } + ] + } +} +``` + +### allow: string[] + +An array of non-standard property names to allow. + +### allowTestedProperty: boolean + +Configure the allowTestedProperty mode for only this rule. +This is prior to the `settings['es-x'].allowTestedProperty` setting. + +## 📚 References + +- [Rule source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/lib/rules/no-nonstandard-asyncdisposablestack-prototype-properties.js) +- [Test source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/tests/lib/rules/no-nonstandard-asyncdisposablestack-prototype-properties.js) diff --git a/docs/rules/no-nonstandard-disposablestack-properties.md b/docs/rules/no-nonstandard-disposablestack-properties.md new file mode 100644 index 00000000..ca21a2a4 --- /dev/null +++ b/docs/rules/no-nonstandard-disposablestack-properties.md @@ -0,0 +1,56 @@ +--- +title: "es-x/no-nonstandard-disposablestack-properties" +description: "disallow non-standard static properties on `DisposableStack` class" +--- + +# es-x/no-nonstandard-disposablestack-properties +> disallow non-standard static properties on `DisposableStack` class + +- ❗ ***This rule has not been released yet.*** + +This rule reports non-standard static properties on `DisposableStack` class as errors. + +## 💡 Examples + +⛔ Examples of **incorrect** code for this rule: + + + +```js +/*eslint es-x/no-nonstandard-disposablestack-properties: error */ +DisposableStack.unknown(); +``` + + + +## 🔧 Options + +This rule has an option. + +```jsonc +{ + "rules": { + "es-x/no-nonstandard-disposablestack-properties": [ + "error", + { + "allow": [], + "allowTestedProperty": false + } + ] + } +} +``` + +### allow: string[] + +An array of non-standard property names to allow. + +### allowTestedProperty: boolean + +Configure the allowTestedProperty mode for only this rule. +This is prior to the `settings['es-x'].allowTestedProperty` setting. + +## 📚 References + +- [Rule source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/lib/rules/no-nonstandard-disposablestack-properties.js) +- [Test source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/tests/lib/rules/no-nonstandard-disposablestack-properties.js) diff --git a/docs/rules/no-nonstandard-disposablestack-prototype-properties.md b/docs/rules/no-nonstandard-disposablestack-prototype-properties.md new file mode 100644 index 00000000..a4c0cee7 --- /dev/null +++ b/docs/rules/no-nonstandard-disposablestack-prototype-properties.md @@ -0,0 +1,57 @@ +--- +title: "es-x/no-nonstandard-disposablestack-prototype-properties" +description: "disallow non-standard properties on DisposableStack instance" +--- + +# es-x/no-nonstandard-disposablestack-prototype-properties +> disallow non-standard properties on DisposableStack instance + +- ❗ ***This rule has not been released yet.*** + +This rule reports non-standard properties on DisposableStack instance as errors. + +## 💡 Examples + +⛔ Examples of **incorrect** code for this rule: + + + +```js +/*eslint es-x/no-nonstandard-disposablestack-prototype-properties: error */ +const foo = new DisposableStack(); +foo.unknown(); +``` + + + +## 🔧 Options + +This rule has an option. + +```jsonc +{ + "rules": { + "es-x/no-nonstandard-disposablestack-prototype-properties": [ + "error", + { + "allow": [], + "allowTestedProperty": false + } + ] + } +} +``` + +### allow: string[] + +An array of non-standard property names to allow. + +### allowTestedProperty: boolean + +Configure the allowTestedProperty mode for only this rule. +This is prior to the `settings['es-x'].allowTestedProperty` setting. + +## 📚 References + +- [Rule source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/lib/rules/no-nonstandard-disposablestack-prototype-properties.js) +- [Test source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/tests/lib/rules/no-nonstandard-disposablestack-prototype-properties.js) diff --git a/docs/rules/no-suppressederror.md b/docs/rules/no-suppressederror.md new file mode 100644 index 00000000..7b3e2b15 --- /dev/null +++ b/docs/rules/no-suppressederror.md @@ -0,0 +1,33 @@ +--- +title: "es-x/no-suppressederror" +description: "disallow the `SuppressedError` class" +--- + +# es-x/no-suppressederror +> disallow the `SuppressedError` class + +- ❗ ***This rule has not been released yet.*** +- ✅ The following configurations enable this rule: [no-explicit-resource-management] and [no-new-in-esnext] + +This rule reports ES2026 [`SuppressedError` class](https://github.com/tc39/proposal-explicit-resource-management) as errors. + +## 💡 Examples + +⛔ Examples of **incorrect** code for this rule: + + + +```js +/*eslint es-x/no-suppressederror: error */ +let suppressederror = new SuppressedError() +``` + + + +## 📚 References + +- [Rule source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/lib/rules/no-suppressederror.js) +- [Test source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/tests/lib/rules/no-suppressederror.js) + +[no-explicit-resource-management]: ../configs/index.md#no-explicit-resource-management +[no-new-in-esnext]: ../configs/index.md#no-new-in-esnext diff --git a/docs/rules/no-using-declarations.md b/docs/rules/no-using-declarations.md new file mode 100644 index 00000000..0724664c --- /dev/null +++ b/docs/rules/no-using-declarations.md @@ -0,0 +1,43 @@ +--- +title: "es-x/no-using-declarations" +description: "disallow `using` and `await using` declarations" +--- + +# es-x/no-using-declarations +> disallow `using` and `await using` declarations + +- ❗ ***This rule has not been released yet.*** +- ✅ The following configurations enable this rule: [no-explicit-resource-management] and [no-new-in-esnext] + +This rule reports ES2026 [`using` and `await using` declarations](https://github.com/tc39/proposal-explicit-resource-management) as errors. + +## 💡 Examples + +⛔ Examples of **incorrect** code for this rule: + + + +```js +/*eslint es-x/no-using-declarations: error */ + +// async disposal +async function * g() { + using stream = acquireStream(); // block-scoped critical resource + // ... +} // cleanup + +{ + await using obj = g(); // block-scoped declaration + const r = await obj.next(); +} // calls finally blocks in `g` +``` + + + +## 📚 References + +- [Rule source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/lib/rules/no-using-declarations.js) +- [Test source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/tests/lib/rules/no-using-declarations.js) + +[no-explicit-resource-management]: ../configs/index.md#no-explicit-resource-management +[no-new-in-esnext]: ../configs/index.md#no-new-in-esnext diff --git a/lib/configs/flat/no-explicit-resource-management.js b/lib/configs/flat/no-explicit-resource-management.js new file mode 100644 index 00000000..8a77e01c --- /dev/null +++ b/lib/configs/flat/no-explicit-resource-management.js @@ -0,0 +1,19 @@ +/** + * DON'T EDIT THIS FILE. + * This file was generated by "scripts/update-lib-flat-configs.js" script. + */ +"use strict" + +module.exports = { + plugins: { + get "es-x"() { + return require("../../index.js") + }, + }, + rules: { + "es-x/no-asyncdisposablestack": "error", + "es-x/no-disposablestack": "error", + "es-x/no-suppressederror": "error", + "es-x/no-using-declarations": "error", + }, +} diff --git a/lib/configs/flat/no-new-in-esnext.js b/lib/configs/flat/no-new-in-esnext.js index 28cfbc96..0788279e 100644 --- a/lib/configs/flat/no-new-in-esnext.js +++ b/lib/configs/flat/no-new-in-esnext.js @@ -12,7 +12,11 @@ module.exports = { }, rules: { "es-x/no-array-fromasync": "error", + "es-x/no-asyncdisposablestack": "error", + "es-x/no-disposablestack": "error", "es-x/no-error-iserror": "error", + "es-x/no-suppressederror": "error", + "es-x/no-using-declarations": "error", "es-x/no-dataview-prototype-getfloat16-setfloat16": "error", "es-x/no-dynamic-import-options": "error", "es-x/no-float16array": "error", diff --git a/lib/configs/no-explicit-resource-management.js b/lib/configs/no-explicit-resource-management.js new file mode 100644 index 00000000..7e3ee511 --- /dev/null +++ b/lib/configs/no-explicit-resource-management.js @@ -0,0 +1,15 @@ +/** + * DON'T EDIT THIS FILE. + * This file was generated by "scripts/update-lib-configs.js" script. + */ +"use strict" + +module.exports = { + plugins: ["es-x"], + rules: { + "es-x/no-asyncdisposablestack": "error", + "es-x/no-disposablestack": "error", + "es-x/no-suppressederror": "error", + "es-x/no-using-declarations": "error", + }, +} diff --git a/lib/configs/no-new-in-esnext.js b/lib/configs/no-new-in-esnext.js index 0fcfcc43..843a36cf 100644 --- a/lib/configs/no-new-in-esnext.js +++ b/lib/configs/no-new-in-esnext.js @@ -8,7 +8,11 @@ module.exports = { plugins: ["es-x"], rules: { "es-x/no-array-fromasync": "error", + "es-x/no-asyncdisposablestack": "error", + "es-x/no-disposablestack": "error", "es-x/no-error-iserror": "error", + "es-x/no-suppressederror": "error", + "es-x/no-using-declarations": "error", "es-x/no-dataview-prototype-getfloat16-setfloat16": "error", "es-x/no-dynamic-import-options": "error", "es-x/no-float16array": "error", diff --git a/lib/index.js b/lib/index.js index 70df62d5..a2f2c106 100644 --- a/lib/index.js +++ b/lib/index.js @@ -13,6 +13,7 @@ module.exports = { "flat/no-array-grouping": require("./configs/flat/no-array-grouping"), "flat/no-change-array-by-copy": require("./configs/flat/no-change-array-by-copy"), "flat/no-class-fields": require("./configs/flat/no-class-fields"), + "flat/no-explicit-resource-management": require("./configs/flat/no-explicit-resource-management"), "flat/no-float16array": require("./configs/flat/no-float16array"), "flat/no-import-attributes": require("./configs/flat/no-import-attributes"), "flat/no-intl-numberformat-v3": require("./configs/flat/no-intl-numberformat-v3"), @@ -67,6 +68,7 @@ module.exports = { "no-array-grouping": require("./configs/no-array-grouping"), "no-change-array-by-copy": require("./configs/no-change-array-by-copy"), "no-class-fields": require("./configs/no-class-fields"), + "no-explicit-resource-management": require("./configs/no-explicit-resource-management"), "no-float16array": require("./configs/no-float16array"), "no-import-attributes": require("./configs/no-import-attributes"), "no-intl-numberformat-v3": require("./configs/no-intl-numberformat-v3"), @@ -179,6 +181,7 @@ module.exports = { "no-arrow-functions": require("./rules/no-arrow-functions"), "no-async-functions": require("./rules/no-async-functions"), "no-async-iteration": require("./rules/no-async-iteration"), + "no-asyncdisposablestack": require("./rules/no-asyncdisposablestack"), "no-atomics": require("./rules/no-atomics"), "no-atomics-waitasync": require("./rules/no-atomics-waitasync"), "no-bigint": require("./rules/no-bigint"), @@ -199,6 +202,7 @@ module.exports = { "no-date-prototype-togmtstring": require("./rules/no-date-prototype-togmtstring"), "no-default-parameters": require("./rules/no-default-parameters"), "no-destructuring": require("./rules/no-destructuring"), + "no-disposablestack": require("./rules/no-disposablestack"), "no-dynamic-import": require("./rules/no-dynamic-import"), "no-dynamic-import-options": require("./rules/no-dynamic-import-options"), "no-error-cause": require("./rules/no-error-cause"), @@ -277,6 +281,8 @@ module.exports = { "no-nonstandard-array-prototype-properties": require("./rules/no-nonstandard-array-prototype-properties"), "no-nonstandard-arraybuffer-properties": require("./rules/no-nonstandard-arraybuffer-properties"), "no-nonstandard-arraybuffer-prototype-properties": require("./rules/no-nonstandard-arraybuffer-prototype-properties"), + "no-nonstandard-asyncdisposablestack-properties": require("./rules/no-nonstandard-asyncdisposablestack-properties"), + "no-nonstandard-asyncdisposablestack-prototype-properties": require("./rules/no-nonstandard-asyncdisposablestack-prototype-properties"), "no-nonstandard-atomics-properties": require("./rules/no-nonstandard-atomics-properties"), "no-nonstandard-bigint-properties": require("./rules/no-nonstandard-bigint-properties"), "no-nonstandard-bigint-prototype-properties": require("./rules/no-nonstandard-bigint-prototype-properties"), @@ -286,6 +292,8 @@ module.exports = { "no-nonstandard-dataview-prototype-properties": require("./rules/no-nonstandard-dataview-prototype-properties"), "no-nonstandard-date-properties": require("./rules/no-nonstandard-date-properties"), "no-nonstandard-date-prototype-properties": require("./rules/no-nonstandard-date-prototype-properties"), + "no-nonstandard-disposablestack-properties": require("./rules/no-nonstandard-disposablestack-properties"), + "no-nonstandard-disposablestack-prototype-properties": require("./rules/no-nonstandard-disposablestack-prototype-properties"), "no-nonstandard-error-properties": require("./rules/no-nonstandard-error-properties"), "no-nonstandard-finalizationregistry-properties": require("./rules/no-nonstandard-finalizationregistry-properties"), "no-nonstandard-finalizationregistry-prototype-properties": require("./rules/no-nonstandard-finalizationregistry-prototype-properties"), @@ -444,6 +452,7 @@ module.exports = { "no-string-prototype-trimstart-trimend": require("./rules/no-string-prototype-trimstart-trimend"), "no-string-raw": require("./rules/no-string-raw"), "no-subclassing-builtins": require("./rules/no-subclassing-builtins"), + "no-suppressederror": require("./rules/no-suppressederror"), "no-symbol": require("./rules/no-symbol"), "no-symbol-prototype-description": require("./rules/no-symbol-prototype-description"), "no-template-literals": require("./rules/no-template-literals"), @@ -453,6 +462,7 @@ module.exports = { "no-trailing-function-commas": require("./rules/no-trailing-function-commas"), "no-typed-arrays": require("./rules/no-typed-arrays"), "no-unicode-codepoint-escapes": require("./rules/no-unicode-codepoint-escapes"), + "no-using-declarations": require("./rules/no-using-declarations"), "no-weak-map": require("./rules/no-weak-map"), "no-weak-set": require("./rules/no-weak-set"), "no-weakrefs": require("./rules/no-weakrefs"), diff --git a/lib/rules/no-asyncdisposablestack.js b/lib/rules/no-asyncdisposablestack.js new file mode 100644 index 00000000..649262c8 --- /dev/null +++ b/lib/rules/no-asyncdisposablestack.js @@ -0,0 +1,24 @@ +"use strict" + +const { defineGlobalsHandler } = require("../util/define-globals-handler") + +module.exports = { + meta: { + docs: { + description: "disallow the `AsyncDisposableStack` class.", + category: "ES2026", + proposal: "explicit-resource-management", + recommended: false, + url: "http://eslint-community.github.io/eslint-plugin-es-x/rules/no-asyncdisposablestack.html", + }, + fixable: null, + messages: { + forbidden: "ES2026 '{{name}}' class is forbidden.", + }, + schema: [], + type: "problem", + }, + create(context) { + return defineGlobalsHandler(context, ["AsyncDisposableStack"]) + }, +} diff --git a/lib/rules/no-disposablestack.js b/lib/rules/no-disposablestack.js new file mode 100644 index 00000000..e9371607 --- /dev/null +++ b/lib/rules/no-disposablestack.js @@ -0,0 +1,24 @@ +"use strict" + +const { defineGlobalsHandler } = require("../util/define-globals-handler") + +module.exports = { + meta: { + docs: { + description: "disallow the `DisposableStack` class.", + category: "ES2026", + proposal: "explicit-resource-management", + recommended: false, + url: "http://eslint-community.github.io/eslint-plugin-es-x/rules/no-disposablestack.html", + }, + fixable: null, + messages: { + forbidden: "ES2026 '{{name}}' class is forbidden.", + }, + schema: [], + type: "problem", + }, + create(context) { + return defineGlobalsHandler(context, ["DisposableStack"]) + }, +} diff --git a/lib/rules/no-nonstandard-asyncdisposablestack-properties.js b/lib/rules/no-nonstandard-asyncdisposablestack-properties.js new file mode 100644 index 00000000..085cb2df --- /dev/null +++ b/lib/rules/no-nonstandard-asyncdisposablestack-properties.js @@ -0,0 +1,49 @@ +"use strict" + +const { + defineNonstandardStaticPropertiesHandler, +} = require("../util/define-nonstandard-static-properties-handler") +const { + asyncDisposableStackProperties, +} = require("../util/well-known-properties") + +module.exports = { + meta: { + docs: { + description: + "disallow non-standard static properties on `AsyncDisposableStack` class", + category: "nonstandard", + recommended: false, + url: "http://eslint-community.github.io/eslint-plugin-es-x/rules/no-nonstandard-asyncdisposablestack-properties.html", + }, + fixable: null, + messages: { + forbidden: "Non-standard '{{name}}' property is forbidden.", + }, + schema: [ + { + type: "object", + properties: { + allow: { + type: "array", + items: { type: "string" }, + uniqueItems: true, + }, + allowTestedProperty: { type: "boolean" }, + }, + additionalProperties: false, + }, + ], + type: "problem", + }, + create(context) { + /** @type {Set} */ + const allows = new Set([ + ...(context.options[0]?.allow || []), + ...asyncDisposableStackProperties, + ]) + return defineNonstandardStaticPropertiesHandler(context, { + AsyncDisposableStack: allows, + }) + }, +} diff --git a/lib/rules/no-nonstandard-asyncdisposablestack-prototype-properties.js b/lib/rules/no-nonstandard-asyncdisposablestack-prototype-properties.js new file mode 100644 index 00000000..c37c6fc1 --- /dev/null +++ b/lib/rules/no-nonstandard-asyncdisposablestack-prototype-properties.js @@ -0,0 +1,49 @@ +"use strict" + +const { + defineNonstandardPrototypePropertiesHandler, +} = require("../util/define-nonstandard-prototype-properties-handler") +const { + asyncDisposableStackPrototypeProperties, +} = require("../util/well-known-properties") + +module.exports = { + meta: { + docs: { + description: + "disallow non-standard properties on AsyncDisposableStack instance", + category: "nonstandard", + recommended: false, + url: "http://eslint-community.github.io/eslint-plugin-es-x/rules/no-nonstandard-asyncdisposablestack-prototype-properties.html", + }, + fixable: null, + messages: { + forbidden: "Non-standard '{{name}}' property is forbidden.", + }, + schema: [ + { + type: "object", + properties: { + allow: { + type: "array", + items: { type: "string" }, + uniqueItems: true, + }, + allowTestedProperty: { type: "boolean" }, + }, + additionalProperties: false, + }, + ], + type: "problem", + }, + create(context) { + /** @type {Set} */ + const allows = new Set([ + ...(context.options[0]?.allow || []), + ...asyncDisposableStackPrototypeProperties, + ]) + return defineNonstandardPrototypePropertiesHandler(context, { + AsyncDisposableStack: allows, + }) + }, +} diff --git a/lib/rules/no-nonstandard-disposablestack-properties.js b/lib/rules/no-nonstandard-disposablestack-properties.js new file mode 100644 index 00000000..ee9b1464 --- /dev/null +++ b/lib/rules/no-nonstandard-disposablestack-properties.js @@ -0,0 +1,47 @@ +"use strict" + +const { + defineNonstandardStaticPropertiesHandler, +} = require("../util/define-nonstandard-static-properties-handler") +const { disposableStackProperties } = require("../util/well-known-properties") + +module.exports = { + meta: { + docs: { + description: + "disallow non-standard static properties on `DisposableStack` class", + category: "nonstandard", + recommended: false, + url: "http://eslint-community.github.io/eslint-plugin-es-x/rules/no-nonstandard-disposablestack-properties.html", + }, + fixable: null, + messages: { + forbidden: "Non-standard '{{name}}' property is forbidden.", + }, + schema: [ + { + type: "object", + properties: { + allow: { + type: "array", + items: { type: "string" }, + uniqueItems: true, + }, + allowTestedProperty: { type: "boolean" }, + }, + additionalProperties: false, + }, + ], + type: "problem", + }, + create(context) { + /** @type {Set} */ + const allows = new Set([ + ...(context.options[0]?.allow || []), + ...disposableStackProperties, + ]) + return defineNonstandardStaticPropertiesHandler(context, { + DisposableStack: allows, + }) + }, +} diff --git a/lib/rules/no-nonstandard-disposablestack-prototype-properties.js b/lib/rules/no-nonstandard-disposablestack-prototype-properties.js new file mode 100644 index 00000000..a1b72323 --- /dev/null +++ b/lib/rules/no-nonstandard-disposablestack-prototype-properties.js @@ -0,0 +1,49 @@ +"use strict" + +const { + defineNonstandardPrototypePropertiesHandler, +} = require("../util/define-nonstandard-prototype-properties-handler") +const { + disposableStackPrototypeProperties, +} = require("../util/well-known-properties") + +module.exports = { + meta: { + docs: { + description: + "disallow non-standard properties on DisposableStack instance", + category: "nonstandard", + recommended: false, + url: "http://eslint-community.github.io/eslint-plugin-es-x/rules/no-nonstandard-disposablestack-prototype-properties.html", + }, + fixable: null, + messages: { + forbidden: "Non-standard '{{name}}' property is forbidden.", + }, + schema: [ + { + type: "object", + properties: { + allow: { + type: "array", + items: { type: "string" }, + uniqueItems: true, + }, + allowTestedProperty: { type: "boolean" }, + }, + additionalProperties: false, + }, + ], + type: "problem", + }, + create(context) { + /** @type {Set} */ + const allows = new Set([ + ...(context.options[0]?.allow || []), + ...disposableStackPrototypeProperties, + ]) + return defineNonstandardPrototypePropertiesHandler(context, { + DisposableStack: allows, + }) + }, +} diff --git a/lib/rules/no-suppressederror.js b/lib/rules/no-suppressederror.js new file mode 100644 index 00000000..5da34f79 --- /dev/null +++ b/lib/rules/no-suppressederror.js @@ -0,0 +1,24 @@ +"use strict" + +const { defineGlobalsHandler } = require("../util/define-globals-handler") + +module.exports = { + meta: { + docs: { + description: "disallow the `SuppressedError` class.", + category: "ES2026", + proposal: "explicit-resource-management", + recommended: false, + url: "http://eslint-community.github.io/eslint-plugin-es-x/rules/no-suppressederror.html", + }, + fixable: null, + messages: { + forbidden: "ES2026 '{{name}}' class is forbidden.", + }, + schema: [], + type: "problem", + }, + create(context) { + return defineGlobalsHandler(context, ["SuppressedError"]) + }, +} diff --git a/lib/rules/no-using-declarations.js b/lib/rules/no-using-declarations.js new file mode 100644 index 00000000..4e2ecfae --- /dev/null +++ b/lib/rules/no-using-declarations.js @@ -0,0 +1,34 @@ +"use strict" + +module.exports = { + meta: { + docs: { + description: "disallow `using` and `await using` declarations", + category: "ES2026", + proposal: "explicit-resource-management", + recommended: false, + url: "http://eslint-community.github.io/eslint-plugin-es-x/rules/no-using-declarations.html", + }, + fixable: null, + messages: { + forbidden: "ES2026 '{{kind}}' declarations are forbidden.", + }, + schema: [], + type: "problem", + }, + create(context) { + return { + VariableDeclaration(node) { + if (node.kind === "using" || node.kind === "await using") { + context.report({ + node, + messageId: "forbidden", + data: { + kind: node.kind, + }, + }) + } + }, + } + }, +} diff --git a/lib/util/type-checker/es-types.js b/lib/util/type-checker/es-types.js index 392b3f63..610576fd 100644 --- a/lib/util/type-checker/es-types.js +++ b/lib/util/type-checker/es-types.js @@ -367,6 +367,20 @@ const WELLKNOWN_GLOBALS = { from: { type: "Function", return: { type: "Iterator" } }, }, }, + DisposableStack: { + type: "Function", + return: { type: "DisposableStack" }, + prototypeType: "DisposableStack", + /** @type {Record} */ + properties: {}, + }, + AsyncDisposableStack: { + type: "Function", + return: { type: "AsyncDisposableStack" }, + prototypeType: "AsyncDisposableStack", + /** @type {Record} */ + properties: {}, + }, WeakRef: { type: "Function", return: { type: "WeakRef" }, @@ -815,6 +829,24 @@ const WELLKNOWN_PROTOTYPE = /** @type {WellKnownPrototypes} */ ({ every: RETURN_BOOLEAN, find: { type: "Function" }, }, + /** @type {Record} */ + DisposableStack: { + adopt: { type: "Function" }, + dispose: { type: "Function" }, + defer: { type: "Function" }, + disposed: { type: "Boolean" }, + move: { type: "Function", return: { type: "DisposableStack" } }, + use: { type: "Function" }, + }, + /** @type {Record} */ + AsyncDisposableStack: { + adopt: { type: "Function", return: { type: "Promise" } }, + disposeAsync: { type: "Function" }, + defer: { type: "Function" }, + disposed: { type: "Boolean" }, + move: { type: "Function", return: { type: "AsyncDisposableStack" } }, + use: { type: "Function" }, + }, /** @type {Record} */ WeakRef: { deref: { type: "Function" }, diff --git a/lib/util/type-checker/types.d.ts b/lib/util/type-checker/types.d.ts index 5ee56e52..ce1d0d75 100644 --- a/lib/util/type-checker/types.d.ts +++ b/lib/util/type-checker/types.d.ts @@ -36,6 +36,8 @@ export type TypeName = | "Boolean" | "BigInt" | "Iterator" + | "DisposableStack" + | "AsyncDisposableStack" | "Map" | "Set" | "WeakMap" @@ -79,6 +81,7 @@ type ExcludePrototypeProperty = | typeof Symbol.matchAll | typeof Symbol.species | typeof Symbol.dispose + | typeof Symbol.asyncDispose | typeof Symbol.metadata; export type ObjectProperty = keyof typeof Object; @@ -209,6 +212,16 @@ export type IteratorPrototypeProperty = Exclude< keyof IteratorObject, ExcludePrototypeProperty >; +export type DisposableStackProperty = Exclude; +export type DisposableStackPrototypeProperty = Exclude< + keyof DisposableStack, + ExcludePrototypeProperty +>; +export type AsyncDisposableStackProperty = Exclude; +export type AsyncDisposableStackPrototypeProperty = Exclude< + keyof AsyncDisposableStack, + ExcludePrototypeProperty +>; export type TypedArrayProperty = Exclude< keyof typeof Int8Array, ExcludeProperty diff --git a/lib/util/well-known-properties.js b/lib/util/well-known-properties.js index 7f90fb98..e57adfa4 100644 --- a/lib/util/well-known-properties.js +++ b/lib/util/well-known-properties.js @@ -95,7 +95,9 @@ const symbolProperties = new Set([ ...functionPrototypeProperties, // https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-properties-of-the-symbol-constructor + "asyncDispose", "asyncIterator", + "dispose", "for", "hasInstance", "isConcatSpreadable", @@ -889,6 +891,51 @@ const iteratorPrototypeProperties = new Set([ // [ %Symbol.asyncIterator%% ] ]) +const disposableStackProperties = new Set([ + ...objectPrototypeProperties, + ...functionPrototypeProperties, + + // https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-properties-of-the-disposablestack-constructor + "prototype", +]) + +const disposableStackPrototypeProperties = new Set([ + ...objectPrototypeProperties, + + // https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-properties-of-the-disposablestack-prototype-object + "adopt", + "constructor", + "defer", + "dispose", + "disposed", + "move", + "use", + // [ %Symbol.dispose% ] + // [ %Symbol.toStringTag% ] +]) + +const asyncDisposableStackProperties = new Set([ + ...objectPrototypeProperties, + ...functionPrototypeProperties, + + // https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-properties-of-the-asyncdisposablestack-constructor + "prototype", +]) + +const asyncDisposableStackPrototypeProperties = new Set([ + ...objectPrototypeProperties, + // https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-properties-of-the-asyncdisposablestack-prototype-object + "adopt", + "constructor", + "defer", + "disposeAsync", + "disposed", + "move", + "use", + // [ %Symbol.asyncDispose% ] + // [ %Symbol.toStringTag% ] +]) + const promiseProperties = new Set([ ...objectPrototypeProperties, ...functionPrototypeProperties, @@ -1228,6 +1275,11 @@ module.exports = { finalizationRegistryPrototypeProperties, iteratorProperties, iteratorPrototypeProperties, + disposableStackProperties, + disposableStackPrototypeProperties, + asyncDisposableStackProperties, + asyncDisposableStackPrototypeProperties, + promiseProperties, promisePrototypeProperties, reflectProperties, diff --git a/package.json b/package.json index ad3bcc80..e3ca6eda 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "eslint-compat-utils": "^0.6.3" }, "devDependencies": { + "@clack/prompts": "^0.11.0", "@typescript-eslint/parser": "^8.0.0", "env-cmd": "^10.1.0", "eslint": "^9.1.0", @@ -27,7 +28,7 @@ "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-vue": "^10.0.0", "espree": "^10.3.0", - "globals": "^16.0.0", + "globals": "^16.2.0", "jsdom": "^26.0.0", "mocha": "^11.0.0", "monaco-editor": "^0.52.0", diff --git a/scripts/new-rule.js b/scripts/new-rule.js index 8a87cd34..cd95a6b5 100644 --- a/scripts/new-rule.js +++ b/scripts/new-rule.js @@ -7,13 +7,21 @@ const cp = require("child_process") const fs = require("fs") const path = require("path") +const prompts = require("@clack/prompts") const { LATEST_ES_YEAR } = require("./rules") const logger = console const maxESVersion = LATEST_ES_YEAR + 1 +main( + String(process.argv[2]) + .toLowerCase() + .replace(/[.]/gu, "-") + .replace(/[()]/gu, ""), +) + // main -;((ruleId) => { +async function main(ruleId) { if (ruleId == null) { logger.error("Usage: npm run new ") process.exitCode = 1 @@ -29,9 +37,548 @@ const maxESVersion = LATEST_ES_YEAR + 1 const testFile = path.resolve(__dirname, `../tests/lib/rules/${ruleId}.js`) const docFile = path.resolve(__dirname, `../docs/rules/${ruleId}.md`) - fs.writeFileSync( - ruleFile, - `"use strict" + prompts.intro("Create the new rule!") + + const kind = await unwrapPrompt( + prompts.select({ + message: `What kind of rule is ${ruleId}?`, + options: [ + { + value: "global-object", + label: "The rule forbids the use of global objects.", + }, + // { + // value: "static-properties", + // label: "The rule forbids the use of static properties.", + // }, + // { + // value: "prototype-properties", + // label: "The rule forbids the use of prototype properties.", + // }, + { + value: "nonstandard-static-properties", + label: "The rule forbids the use of non-standard static properties.", + }, + { + value: "nonstandard-prototype-properties", + label: "The rule forbids the use of non-standard prototype properties.", + }, + { + value: "default", + label: "There is no option", + }, + ], + }), + ) + + const resourceOptions = { + ruleId, + kind, + object: "", + properties: /** @type {string[]} */ [], + } + + if ( + kind === "global-object" || + kind === "static-properties" || + kind === "prototype-properties" || + kind === "nonstandard-static-properties" || + kind === "nonstandard-prototype-properties" + ) { + resourceOptions.object = await unwrapPrompt( + prompts.text({ + message: "What is the global object that the rule checks?", + placeholder: "e.g. Set, Map, Math", + validate(value) { + if (value.trim().length === 0) { + return "The global object name must not be empty." + } + return undefined + }, + }), + ) + } + + if (kind === "static-properties" || kind === "prototype-properties") { + const promptObject = globalThis[resourceOptions.object] + const promptProperties = promptObject + ? kind === "static-properties" + ? Object.getOwnPropertyNames(promptObject) + : Object.getOwnPropertyNames(promptObject.prototype) + : [] + resourceOptions.properties = ( + await unwrapPrompt( + prompts.text({ + message: "What is the property names that the rule checks?", + placeholder: promptProperties.length + ? `e.g. ${promptProperties.join(", ")}` + : undefined, + validate(value) { + const names = value + .split(",") + .map((s) => s.trim()) + .filter((s) => s) + if (names.length === 0) { + return "The property names must not be empty." + } + return undefined + }, + }), + ) + ) + .split(",") + .map((s) => s.trim()) + .filter((s) => s) + } + + const resources = + kind === "global-object" + ? buildGlobalObjectRuleResources(resourceOptions) + : kind === "nonstandard-static-properties" + ? buildNonStandardStaticPropertiesRuleResources(resourceOptions) + : kind === "nonstandard-prototype-properties" + ? buildNonStandardPrototypePropertiesRuleResources( + resourceOptions, + ) + : buildDefaultResources(resourceOptions) + + fs.writeFileSync(ruleFile, resources.rule) + fs.writeFileSync(testFile, resources.test) + fs.writeFileSync(docFile, resources.doc) + + cp.execSync(`code "${ruleFile}"`) + cp.execSync(`code "${testFile}"`) + cp.execSync(`code "${docFile}"`) + + const yellow = "\u001b[33m" + + const reset = "\u001b[0m" + + console.log(`Test Command: + +${yellow}npx mocha "tests/**/${ruleId}.js" --reporter dot --timeout 60000${reset} + +`) +} + +/** + * @template T + * @param {Promise} maybeCancelPromise + * @returns {Promise} + */ +async function unwrapPrompt(maybeCancelPromise) { + const result = await maybeCancelPromise + + if (prompts.isCancel(result)) { + prompts.cancel("✖ Operation cancelled") + // eslint-disable-next-line no-process-exit + process.exit(0) + } + return result +} + +function buildGlobalObjectRuleResources({ ruleId, object }) { + return { + rule: `"use strict" + +const { defineGlobalsHandler } = require("../util/define-globals-handler") + +module.exports = { + meta: { + docs: { + description: "disallow the \`${object}\` class.", + category: "ES${maxESVersion}", + recommended: false, + url: "", + }, + fixable: null, + messages: { + forbidden: "ES${maxESVersion} '{{name}}' class is forbidden.", + }, + schema: [], + type: "problem", + }, + create(context) { + return defineGlobalsHandler(context, ["${object}"]) + }, +} +`, + test: `"use strict" + +const RuleTester = require("../../tester") +const rule = require("../../../lib/rules/${ruleId}.js") + +new RuleTester().run("${ruleId}", rule, { + valid: ["Array", "Object", "let ${object} = 0; ${object}"], + invalid: [ + { + code: "${object}", + errors: ["ES${maxESVersion} '${object}' class is forbidden."], + }, + { + code: "function f() { ${object} }", + errors: ["ES${maxESVersion} '${object}' class is forbidden."], + }, + ], +}) +`, + doc: `# es-x/${ruleId} +> + +This rule reports ES${maxESVersion} [\`${object}\` class]($$LINK$$) as errors. + +## 💡 Examples + +⛔ Examples of **incorrect** code for this rule: + + + +\`\`\`js +/*eslint es-x/${ruleId}: error */ +let ${object.toLowerCase()} = new ${object}() +\`\`\` + + +`, + } +} + +function buildNonStandardStaticPropertiesRuleResources({ ruleId, object }) { + const camelObject = camelCase(object) + return { + rule: `"use strict" + +const { + defineNonstandardStaticPropertiesHandler, +} = require("../util/define-nonstandard-static-properties-handler") +const { ${camelObject}Properties } = require("../util/well-known-properties") + +module.exports = { + meta: { + docs: { + description: "disallow non-standard static properties on \`${object}\` class", + category: "nonstandard", + recommended: false, + url: "", + }, + fixable: null, + messages: { + forbidden: "Non-standard '{{name}}' property is forbidden.", + }, + schema: [ + { + type: "object", + properties: { + allow: { + type: "array", + items: { type: "string" }, + uniqueItems: true, + }, + allowTestedProperty: { type: "boolean" }, + }, + additionalProperties: false, + }, + ], + type: "problem", + }, + create(context) { + /** @type {Set} */ + const allows = new Set([ + ...(context.options[0]?.allow || []), + ...${camelObject}Properties, + ]) + return defineNonstandardStaticPropertiesHandler(context, { + '${object}': allows, + }) + }, +} +`, + test: `"use strict" + +const RuleTester = require("../../tester") +const rule = require("../../../lib/rules/${ruleId}.js") +const { ${camelObject}Properties } = require("../../../lib/util/well-known-properties") + +new RuleTester().run("${ruleId}", rule, { + valid: [ + ...[...${camelObject}Properties].map((p) => \`${object}.\${p}\`), + { code: "${object}.unknown()", options: [{ allow: ["unknown"] }] }, + ], + invalid: [ + { + code: "${object}.unknown()", + errors: ["Non-standard '${object}.unknown' property is forbidden."], + }, + { + code: "${object}.foo", + errors: ["Non-standard '${object}.foo' property is forbidden."], + }, + ], +}) +`, + doc: `# es-x/${ruleId} +> + +This rule reports non-standard static properties on \`${object}\` class as errors. + +## 💡 Examples + +⛔ Examples of **incorrect** code for this rule: + + + +\`\`\`js +/*eslint es-x/${ruleId}: error */ +${object}.unknown(); +\`\`\` + + + +## 🔧 Options + +This rule has an option. + +\`\`\`jsonc +{ + "rules": { + "es-x/${ruleId}": [ + "error", + { + "allow": [], + "allowTestedProperty": false + } + ] + } +} +\`\`\` + +### allow: string[] + +An array of non-standard property names to allow. + +### allowTestedProperty: boolean + +Configure the allowTestedProperty mode for only this rule. +This is prior to the \`settings['es-x'].allowTestedProperty\` setting. +`, + } +} + +function buildNonStandardPrototypePropertiesRuleResources({ ruleId, object }) { + const camelObject = camelCase(object) + return { + rule: `"use strict" + +const { + defineNonstandardPrototypePropertiesHandler, +} = require("../util/define-nonstandard-prototype-properties-handler") +const { ${camelObject}PrototypeProperties } = require("../util/well-known-properties") + +module.exports = { + meta: { + docs: { + description: "disallow non-standard properties on ${object} instance", + category: "nonstandard", + recommended: false, + url: "", + }, + fixable: null, + messages: { + forbidden: "Non-standard '{{name}}' property is forbidden.", + }, + schema: [ + { + type: "object", + properties: { + allow: { + type: "array", + items: { type: "string" }, + uniqueItems: true, + }, + allowTestedProperty: { type: "boolean" }, + }, + additionalProperties: false, + }, + ], + type: "problem", + }, + create(context) { + /** @type {Set} */ + const allows = new Set([ + ...(context.options[0]?.allow || []), + ...${camelObject}PrototypeProperties, + ]) + return defineNonstandardPrototypePropertiesHandler(context, { + '${object}': allows, + }) + }, +} +`, + test: `"use strict" + +const path = require("path") +const RuleTester = require("../../tester") +const rule = require("../../../lib/rules/${ruleId}.js") +const { + ${camelObject}PrototypeProperties, +} = require("../../../lib/util/well-known-properties") +const ruleId = "${ruleId}" + +new RuleTester().run(ruleId, rule, { + valid: [ + "foo", + "foo.toString", + "foo.foo", + ...[...${camelObject}PrototypeProperties].map((p) => \`(new ${object}()).\${p}\`), + { code: "(new ${object}()).unknown()", options: [{ allow: ["unknown"] }] }, + ], + invalid: [ + { + code: "(new ${object}()).unknown()", + errors: [ + "Non-standard '${object}.prototype.unknown' property is forbidden.", + ], + }, + { + code: "(new ${object}()).foo", + errors: [ + "Non-standard '${object}.prototype.foo' property is forbidden.", + ], + }, + { + code: "(new ${object}())[0]", + errors: [ + "Non-standard '${object}.prototype.0' property is forbidden.", + ], + }, + { + code: "(new ${object}())['01']", + errors: [ + "Non-standard '${object}.prototype.01' property is forbidden.", + ], + }, + ], +}) + +// ----------------------------------------------------------------------------- +// TypeScript +// ----------------------------------------------------------------------------- +const parser = require("@typescript-eslint/parser") +const tsconfigRootDir = path.resolve(__dirname, "../../fixtures") +const project = "tsconfig.json" +const filename = path.join(tsconfigRootDir, "test.ts") + +new RuleTester({ + languageOptions: { + parser, + parserOptions: { + tsconfigRootDir, + project, + disallowAutomaticSingleRunInference: true, + }, + }, +}).run(\`${ruleId} TS Full Type Information\`, rule, { + valid: [ + { filename, code: "foo" }, + { filename, code: "foo.toString" }, + { filename, code: "foo.foo" }, + { filename, code: "let foo = {}; foo.foo" }, + ...[...${camelObject}PrototypeProperties].map((p) => ({ + filename, + code: \`(new ${object}()).\${p}\`, + })), + ], + invalid: [ + { + filename, + code: "(new ${object}()).foo", + errors: [ + "Non-standard '${object}.prototype.foo' property is forbidden.", + ], + }, + { + filename, + code: "(new ${object}())[0]", + errors: [ + "Non-standard '${object}.prototype.0' property is forbidden.", + ], + }, + { + filename, + code: "(new ${object}())['01']", + errors: [ + "Non-standard '${object}.prototype.01' property is forbidden.", + ], + }, + { + filename, + code: "let foo = (new ${object}()); foo.foo", + errors: [ + "Non-standard '${object}.prototype.foo' property is forbidden.", + ], + }, + { + filename, + code: "function f(a: T) { a.baz }", + errors: [ + "Non-standard '${object}.prototype.baz' property is forbidden.", + ], + }, + ], +}) +`, + doc: `# es-x/${ruleId} +> + +This rule reports non-standard properties on ${object} instance as errors. + +## 💡 Examples + +⛔ Examples of **incorrect** code for this rule: + + + +\`\`\`js +/*eslint es-x/${ruleId}: error */ +const foo = new ${object}(); +foo.unknown(); +\`\`\` + + + +## 🔧 Options + +This rule has an option. + +\`\`\`jsonc +{ + "rules": { + "es-x/${ruleId}": [ + "error", + { + "allow": [], + "allowTestedProperty": false + } + ] + } +} +\`\`\` + +### allow: string[] + +An array of non-standard property names to allow. + +### allowTestedProperty: boolean + +Configure the allowTestedProperty mode for only this rule. +This is prior to the \`settings['es-x'].allowTestedProperty\` setting. +`, + } +} + +function buildDefaultResources({ ruleId }) { + return { + rule: `"use strict" module.exports = { meta: { @@ -53,10 +600,7 @@ module.exports = { }, } `, - ) - fs.writeFileSync( - testFile, - `"use strict" + test: `"use strict" const RuleTester = require("../../tester") const rule = require("../../../lib/rules/${ruleId}.js") @@ -72,10 +616,7 @@ new RuleTester().run("${ruleId}", rule, { invalid: [], }) `, - ) - fs.writeFileSync( - docFile, - `# es-x/${ruleId} + doc: `# es-x/${ruleId} > This rule reports ??? as errors. @@ -93,24 +634,12 @@ This rule reports ??? as errors. `, - ) - - cp.execSync(`code "${ruleFile}"`) - cp.execSync(`code "${testFile}"`) - cp.execSync(`code "${docFile}"`) - - const yellow = "\u001b[33m" - - const reset = "\u001b[0m" - - console.log(`Test Command: - -${yellow}npx mocha "tests/**/${ruleId}.js" --reporter dot --timeout 60000${reset} + } +} -`) -})( - String(process.argv[2]) - .toLowerCase() - .replace(/[.]/gu, "-") - .replace(/[()]/gu, ""), -) +function camelCase(str) { + const base = /[_.-]/u.test(str) + ? str.replace(/[_.-](\w|$)/gu, (_, x) => x.toUpperCase()) + : str + return `${base[0].toLowerCase()}${base.slice(1)}` +} diff --git a/scripts/proposals.js b/scripts/proposals.js index 4e6dcb1f..091b8473 100644 --- a/scripts/proposals.js +++ b/scripts/proposals.js @@ -13,6 +13,10 @@ module.exports = { title: "Class Fields", link: "https://github.com/tc39/proposal-class-fields", }, + "explicit-resource-management": { + title: "Explicit Resource Management", + link: "https://github.com/tc39/proposal-explicit-resource-management", + }, float16array: { title: "Float16Array", link: "https://github.com/tc39/proposal-float16array", diff --git a/tests/lib/rules/no-asyncdisposablestack.js b/tests/lib/rules/no-asyncdisposablestack.js new file mode 100644 index 00000000..24376e97 --- /dev/null +++ b/tests/lib/rules/no-asyncdisposablestack.js @@ -0,0 +1,22 @@ +"use strict" + +const RuleTester = require("../../tester") +const rule = require("../../../lib/rules/no-asyncdisposablestack.js") + +new RuleTester().run("no-asyncdisposablestack", rule, { + valid: [ + "Array", + "Object", + "let AsyncDisposableStack = 0; AsyncDisposableStack", + ], + invalid: [ + { + code: "AsyncDisposableStack", + errors: ["ES2026 'AsyncDisposableStack' class is forbidden."], + }, + { + code: "function f() { AsyncDisposableStack }", + errors: ["ES2026 'AsyncDisposableStack' class is forbidden."], + }, + ], +}) diff --git a/tests/lib/rules/no-disposablestack.js b/tests/lib/rules/no-disposablestack.js new file mode 100644 index 00000000..a6d670c6 --- /dev/null +++ b/tests/lib/rules/no-disposablestack.js @@ -0,0 +1,18 @@ +"use strict" + +const RuleTester = require("../../tester") +const rule = require("../../../lib/rules/no-disposablestack.js") + +new RuleTester().run("no-disposablestack", rule, { + valid: ["Array", "Object", "let DisposableStack = 0; DisposableStack"], + invalid: [ + { + code: "DisposableStack", + errors: ["ES2026 'DisposableStack' class is forbidden."], + }, + { + code: "function f() { DisposableStack }", + errors: ["ES2026 'DisposableStack' class is forbidden."], + }, + ], +}) diff --git a/tests/lib/rules/no-nonstandard-asyncdisposablestack-properties.js b/tests/lib/rules/no-nonstandard-asyncdisposablestack-properties.js new file mode 100644 index 00000000..4261b3d9 --- /dev/null +++ b/tests/lib/rules/no-nonstandard-asyncdisposablestack-properties.js @@ -0,0 +1,33 @@ +"use strict" + +const RuleTester = require("../../tester") +const rule = require("../../../lib/rules/no-nonstandard-asyncdisposablestack-properties.js") +const { + asyncDisposableStackProperties, +} = require("../../../lib/util/well-known-properties") + +new RuleTester().run("no-nonstandard-asyncdisposablestack-properties", rule, { + valid: [ + ...[...asyncDisposableStackProperties].map( + (p) => `AsyncDisposableStack.${p}`, + ), + { + code: "AsyncDisposableStack.unknown()", + options: [{ allow: ["unknown"] }], + }, + ], + invalid: [ + { + code: "AsyncDisposableStack.unknown()", + errors: [ + "Non-standard 'AsyncDisposableStack.unknown' property is forbidden.", + ], + }, + { + code: "AsyncDisposableStack.foo", + errors: [ + "Non-standard 'AsyncDisposableStack.foo' property is forbidden.", + ], + }, + ], +}) diff --git a/tests/lib/rules/no-nonstandard-asyncdisposablestack-prototype-properties.js b/tests/lib/rules/no-nonstandard-asyncdisposablestack-prototype-properties.js new file mode 100644 index 00000000..ee2fd253 --- /dev/null +++ b/tests/lib/rules/no-nonstandard-asyncdisposablestack-prototype-properties.js @@ -0,0 +1,121 @@ +"use strict" + +const path = require("path") +const RuleTester = require("../../tester") +const rule = require("../../../lib/rules/no-nonstandard-asyncdisposablestack-prototype-properties.js") +const { + asyncDisposableStackPrototypeProperties, +} = require("../../../lib/util/well-known-properties") +const ruleId = "no-nonstandard-asyncdisposablestack-prototype-properties" + +new RuleTester().run(ruleId, rule, { + valid: [ + "foo", + "foo.toString", + "foo.foo", + ...[...asyncDisposableStackPrototypeProperties].map( + (p) => `(new AsyncDisposableStack()).${p}`, + ), + { + code: "(new AsyncDisposableStack()).unknown()", + options: [{ allow: ["unknown"] }], + }, + ], + invalid: [ + { + code: "(new AsyncDisposableStack()).unknown()", + errors: [ + "Non-standard 'AsyncDisposableStack.prototype.unknown' property is forbidden.", + ], + }, + { + code: "(new AsyncDisposableStack()).foo", + errors: [ + "Non-standard 'AsyncDisposableStack.prototype.foo' property is forbidden.", + ], + }, + { + code: "(new AsyncDisposableStack())[0]", + errors: [ + "Non-standard 'AsyncDisposableStack.prototype.0' property is forbidden.", + ], + }, + { + code: "(new AsyncDisposableStack())['01']", + errors: [ + "Non-standard 'AsyncDisposableStack.prototype.01' property is forbidden.", + ], + }, + ], +}) + +// ----------------------------------------------------------------------------- +// TypeScript +// ----------------------------------------------------------------------------- +const parser = require("@typescript-eslint/parser") +const tsconfigRootDir = path.resolve(__dirname, "../../fixtures") +const project = "tsconfig.json" +const filename = path.join(tsconfigRootDir, "test.ts") + +new RuleTester({ + languageOptions: { + parser, + parserOptions: { + tsconfigRootDir, + project, + disallowAutomaticSingleRunInference: true, + }, + }, +}).run( + "no-nonstandard-asyncdisposablestack-prototype-properties TS Full Type Information", + rule, + { + valid: [ + { filename, code: "foo" }, + { filename, code: "foo.toString" }, + { filename, code: "foo.foo" }, + { filename, code: "let foo = {}; foo.foo" }, + ...[...asyncDisposableStackPrototypeProperties].map((p) => ({ + filename, + code: `(new AsyncDisposableStack()).${p}`, + })), + ], + invalid: [ + { + filename, + code: "(new AsyncDisposableStack()).foo", + errors: [ + "Non-standard 'AsyncDisposableStack.prototype.foo' property is forbidden.", + ], + }, + { + filename, + code: "(new AsyncDisposableStack())[0]", + errors: [ + "Non-standard 'AsyncDisposableStack.prototype.0' property is forbidden.", + ], + }, + { + filename, + code: "(new AsyncDisposableStack())['01']", + errors: [ + "Non-standard 'AsyncDisposableStack.prototype.01' property is forbidden.", + ], + }, + { + filename, + code: "let foo = (new AsyncDisposableStack()); foo.foo", + errors: [ + "Non-standard 'AsyncDisposableStack.prototype.foo' property is forbidden.", + ], + }, + { + filename, + code: "function f(a: T) { a.baz }", + errors: [ + "Non-standard 'AsyncDisposableStack.prototype.baz' property is forbidden.", + ], + }, + ], + }, +) diff --git a/tests/lib/rules/no-nonstandard-disposablestack-properties.js b/tests/lib/rules/no-nonstandard-disposablestack-properties.js new file mode 100644 index 00000000..4c91cfed --- /dev/null +++ b/tests/lib/rules/no-nonstandard-disposablestack-properties.js @@ -0,0 +1,31 @@ +"use strict" + +const RuleTester = require("../../tester") +const rule = require("../../../lib/rules/no-nonstandard-disposablestack-properties.js") +const { + disposableStackProperties, +} = require("../../../lib/util/well-known-properties") + +new RuleTester().run("no-nonstandard-disposablestack-properties", rule, { + valid: [ + ...[...disposableStackProperties].map((p) => `DisposableStack.${p}`), + { + code: "DisposableStack.unknown()", + options: [{ allow: ["unknown"] }], + }, + ], + invalid: [ + { + code: "DisposableStack.unknown()", + errors: [ + "Non-standard 'DisposableStack.unknown' property is forbidden.", + ], + }, + { + code: "DisposableStack.foo", + errors: [ + "Non-standard 'DisposableStack.foo' property is forbidden.", + ], + }, + ], +}) diff --git a/tests/lib/rules/no-nonstandard-disposablestack-prototype-properties.js b/tests/lib/rules/no-nonstandard-disposablestack-prototype-properties.js new file mode 100644 index 00000000..8452edfa --- /dev/null +++ b/tests/lib/rules/no-nonstandard-disposablestack-prototype-properties.js @@ -0,0 +1,121 @@ +"use strict" + +const path = require("path") +const RuleTester = require("../../tester") +const rule = require("../../../lib/rules/no-nonstandard-disposablestack-prototype-properties.js") +const { + disposableStackPrototypeProperties, +} = require("../../../lib/util/well-known-properties") +const ruleId = "no-nonstandard-disposablestack-prototype-properties" + +new RuleTester().run(ruleId, rule, { + valid: [ + "foo", + "foo.toString", + "foo.foo", + ...[...disposableStackPrototypeProperties].map( + (p) => `(new DisposableStack()).${p}`, + ), + { + code: "(new DisposableStack()).unknown()", + options: [{ allow: ["unknown"] }], + }, + ], + invalid: [ + { + code: "(new DisposableStack()).unknown()", + errors: [ + "Non-standard 'DisposableStack.prototype.unknown' property is forbidden.", + ], + }, + { + code: "(new DisposableStack()).foo", + errors: [ + "Non-standard 'DisposableStack.prototype.foo' property is forbidden.", + ], + }, + { + code: "(new DisposableStack())[0]", + errors: [ + "Non-standard 'DisposableStack.prototype.0' property is forbidden.", + ], + }, + { + code: "(new DisposableStack())['01']", + errors: [ + "Non-standard 'DisposableStack.prototype.01' property is forbidden.", + ], + }, + ], +}) + +// ----------------------------------------------------------------------------- +// TypeScript +// ----------------------------------------------------------------------------- +const parser = require("@typescript-eslint/parser") +const tsconfigRootDir = path.resolve(__dirname, "../../fixtures") +const project = "tsconfig.json" +const filename = path.join(tsconfigRootDir, "test.ts") + +new RuleTester({ + languageOptions: { + parser, + parserOptions: { + tsconfigRootDir, + project, + disallowAutomaticSingleRunInference: true, + }, + }, +}).run( + "no-nonstandard-disposablestack-prototype-properties TS Full Type Information", + rule, + { + valid: [ + { filename, code: "foo" }, + { filename, code: "foo.toString" }, + { filename, code: "foo.foo" }, + { filename, code: "let foo = {}; foo.foo" }, + ...[...disposableStackPrototypeProperties].map((p) => ({ + filename, + code: `(new DisposableStack()).${p}`, + })), + ], + invalid: [ + { + filename, + code: "(new DisposableStack()).foo", + errors: [ + "Non-standard 'DisposableStack.prototype.foo' property is forbidden.", + ], + }, + { + filename, + code: "(new DisposableStack())[0]", + errors: [ + "Non-standard 'DisposableStack.prototype.0' property is forbidden.", + ], + }, + { + filename, + code: "(new DisposableStack())['01']", + errors: [ + "Non-standard 'DisposableStack.prototype.01' property is forbidden.", + ], + }, + { + filename, + code: "let foo = (new DisposableStack()); foo.foo", + errors: [ + "Non-standard 'DisposableStack.prototype.foo' property is forbidden.", + ], + }, + { + filename, + code: "function f(a: T) { a.baz }", + errors: [ + "Non-standard 'DisposableStack.prototype.baz' property is forbidden.", + ], + }, + ], + }, +) diff --git a/tests/lib/rules/no-suppressederror.js b/tests/lib/rules/no-suppressederror.js new file mode 100644 index 00000000..7c64eeb2 --- /dev/null +++ b/tests/lib/rules/no-suppressederror.js @@ -0,0 +1,18 @@ +"use strict" + +const RuleTester = require("../../tester") +const rule = require("../../../lib/rules/no-suppressederror.js") + +new RuleTester().run("no-suppressederror", rule, { + valid: ["Array", "Object", "let SuppressedError = 0; SuppressedError"], + invalid: [ + { + code: "SuppressedError", + errors: ["ES2026 'SuppressedError' class is forbidden."], + }, + { + code: "function f() { SuppressedError }", + errors: ["ES2026 'SuppressedError' class is forbidden."], + }, + ], +}) diff --git a/tests/lib/rules/no-using-declarations.js b/tests/lib/rules/no-using-declarations.js new file mode 100644 index 00000000..cc944773 --- /dev/null +++ b/tests/lib/rules/no-using-declarations.js @@ -0,0 +1,35 @@ +"use strict" + +const semver = require("semver") +const RuleTester = require("../../tester") +const rule = require("../../../lib/rules/no-using-declarations.js") + +// if (!RuleTester.isSupported(2026)) { +// //eslint-disable-next-line no-console +// console.log("Skip the tests of no-using-declarations.") +// return +// } +if (semver.lt(require("@typescript-eslint/parser").version, "8.0.0")) { + //eslint-disable-next-line no-console + console.log("Skip the tests of no-using-declarations.") + return +} + +new RuleTester({ + languageOptions: { + sourceType: "module", + parser: require("@typescript-eslint/parser"), // espree does not support `using` yet. + }, +}).run("no-using-declarations", rule, { + valid: ["let x = y", "const x = y", "var x = y", "const x = await y"], + invalid: [ + { + code: "using x = y", + errors: ["ES2026 'using' declarations are forbidden."], + }, + { + code: "await using x = y", + errors: ["ES2026 'await using' declarations are forbidden."], + }, + ], +}) diff --git a/tests/tester.js b/tests/tester.js index 5cf3e94d..1a6b1bd7 100644 --- a/tests/tester.js +++ b/tests/tester.js @@ -31,11 +31,9 @@ RuleTester.setDefaultConfig({ ecmaVersion, sourceType: "script", globals: { - AggregateError: "readonly", - FinalizationRegistry: "readonly", - WeakRef: "readonly", - Intl: "readonly", - Float16Array: "readonly", + AsyncDisposableStack: "readonly", + DisposableStack: "readonly", + SuppressedError: "readonly", ...builtin, }, },