From 44f8c41a69167a8088a600de41bdcf3b0719218a Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Tue, 17 Dec 2024 11:11:40 +0900 Subject: [PATCH 1/2] Add rules for `Intl.DurationFormat` --- docs/configs/index.md | 26 +++ docs/rules/index.md | 11 ++ docs/rules/no-intl-durationformat.md | 38 ++++ ...standard-intl-durationformat-properties.md | 50 +++++ ...ntl-durationformat-prototype-properties.md | 51 +++++ lib/configs/flat/no-new-in-esnext-intl-api.js | 2 +- lib/configs/no-new-in-esnext-intl-api.js | 5 +- lib/index.js | 3 + lib/rules/no-intl-durationformat.js | 27 +++ ...standard-intl-durationformat-properties.js | 48 +++++ ...ntl-durationformat-prototype-properties.js | 48 +++++ lib/util/type-checker/es-types.js | 5 + lib/util/type-checker/types.d.ts | 3 +- lib/util/well-known-properties.js | 23 +++ package.json | 2 +- scripts/new-rule.js | 8 +- scripts/rules.js | 4 +- tests/fixtures/tsconfig.json | 2 +- tests/fixtures/types.d.ts | 174 ++++++++++++++++++ tests/lib/rules/no-intl-durationformat.js | 21 +++ ...standard-intl-durationformat-properties.js | 39 ++++ ...ntl-durationformat-prototype-properties.js | 143 ++++++++++++++ 22 files changed, 720 insertions(+), 13 deletions(-) create mode 100644 docs/rules/no-intl-durationformat.md create mode 100644 docs/rules/no-nonstandard-intl-durationformat-properties.md create mode 100644 docs/rules/no-nonstandard-intl-durationformat-prototype-properties.md create mode 100644 lib/rules/no-intl-durationformat.js create mode 100644 lib/rules/no-nonstandard-intl-durationformat-properties.js create mode 100644 lib/rules/no-nonstandard-intl-durationformat-prototype-properties.js create mode 100644 tests/fixtures/types.d.ts create mode 100644 tests/lib/rules/no-intl-durationformat.js create mode 100644 tests/lib/rules/no-nonstandard-intl-durationformat-properties.js create mode 100644 tests/lib/rules/no-nonstandard-intl-durationformat-prototype-properties.js diff --git a/docs/configs/index.md b/docs/configs/index.md index dfd2cdd3..7eb6099f 100644 --- a/docs/configs/index.md +++ b/docs/configs/index.md @@ -28,6 +28,32 @@ export default [ } ``` +## no-new-in-esnext-intl-api + +disallow the new stuff to be planned for the next yearly ECMAScript Intl API (ECMA-402) snapshot.\ +⚠️ This config will be changed in the minor versions of this plugin. + +### [Config (Flat Config)] + +eslint.config.js: + +```js +import pluginESx from "eslint-plugin-es-x" +export default [ + pluginESx.configs['flat/no-new-in-esnext-intl-api'] +] +``` + +### [Legacy Config] + +.eslintrc.*: + +```json +{ + "extends": ["plugin:es-x/no-new-in-esnext-intl-api"], +} +``` + ## no-new-in-es2024 disallow new stuff in ES2024. diff --git a/docs/rules/index.md b/docs/rules/index.md index 59a69421..b1ca2001 100644 --- a/docs/rules/index.md +++ b/docs/rules/index.md @@ -4,6 +4,14 @@ This plugin provides the following rules. - 🔧 mark means that the `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by the rule. +## ES2026 Intl API + +There is a config that enables the rules in this category: [`no-new-in-esnext-intl-api`] + +| Rule ID | Description | | +|:--------|:------------|:--:| +| [es-x/no-intl-durationformat](./no-intl-durationformat.md) | disallow the `Intl.DurationFormat` object. | | + ## ES2025 There is a config that enables the rules in this category: [`no-new-in-esnext`] @@ -406,6 +414,8 @@ Rules in this category are not included in any preset. | [es-x/no-nonstandard-intl-datetimeformat-prototype-properties](./no-nonstandard-intl-datetimeformat-prototype-properties.md) | disallow non-standard properties on Intl.DateTimeFormat instance. | | | [es-x/no-nonstandard-intl-displaynames-properties](./no-nonstandard-intl-displaynames-properties.md) | disallow non-standard static properties on `Intl.DisplayNames` class. | | | [es-x/no-nonstandard-intl-displaynames-prototype-properties](./no-nonstandard-intl-displaynames-prototype-properties.md) | disallow non-standard properties on Intl.DisplayNames instance. | | +| [es-x/no-nonstandard-intl-durationformat-properties](./no-nonstandard-intl-durationformat-properties.md) | disallow non-standard static properties on `Intl.DurationFormat` class. | | +| [es-x/no-nonstandard-intl-durationformat-prototype-properties](./no-nonstandard-intl-durationformat-prototype-properties.md) | disallow non-standard properties on Intl.DurationFormat instance. | | | [es-x/no-nonstandard-intl-listformat-properties](./no-nonstandard-intl-listformat-properties.md) | disallow non-standard static properties on `Intl.ListFormat` class. | | | [es-x/no-nonstandard-intl-listformat-prototype-properties](./no-nonstandard-intl-listformat-prototype-properties.md) | disallow non-standard properties on Intl.ListFormat instance. | | | [es-x/no-nonstandard-intl-locale-properties](./no-nonstandard-intl-locale-properties.md) | disallow non-standard static properties on `Intl.Locale` class. | | @@ -462,6 +472,7 @@ Rules in this category are not included in any preset. | [es-x/no-object-map-groupby](./no-object-map-groupby.md) | [es-x/no-object-groupby](./no-object-groupby.md), [es-x/no-map-groupby](./no-map-groupby.md) | | [es-x/no-string-prototype-iswellformed-towellformed](./no-string-prototype-iswellformed-towellformed.md) | [es-x/no-string-prototype-iswellformed](./no-string-prototype-iswellformed.md), [es-x/no-string-prototype-towellformed](./no-string-prototype-towellformed.md) | +[`no-new-in-esnext-intl-api`]: ../configs/index.md#no-new-in-esnext-intl-api [`no-new-in-esnext`]: ../configs/index.md#no-new-in-esnext [`no-new-in-es2024`]: ../configs/index.md#no-new-in-es2024 [`restrict-to-es2023`]: ../configs/index.md#restrict-to-es2023 diff --git a/docs/rules/no-intl-durationformat.md b/docs/rules/no-intl-durationformat.md new file mode 100644 index 00000000..77340328 --- /dev/null +++ b/docs/rules/no-intl-durationformat.md @@ -0,0 +1,38 @@ +--- +title: "es-x/no-intl-durationformat" +description: "disallow the `Intl.DurationFormat` object" +--- + +# es-x/no-intl-durationformat +> disallow the `Intl.DurationFormat` object + +- ❗ ***This rule has not been released yet.*** +- ✅ The following configurations enable this rule: [no-new-in-esnext-intl-api] + +This rule reports ES2026 Intl API `Intl.DurationFormat` object as errors. + +## 💡 Examples + +⛔ Examples of **incorrect** code for this rule: + + + +```js +/*eslint es-x/no-intl-durationformat: error */ +const df = new Intl.DurationFormat("fr-FR", { style: "long" }); + +df.format({ + hours: 1, + minutes: 46, + seconds: 40, +}); +``` + + + +## 📚 References + +- [Rule source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/lib/rules/no-intl-durationformat.js) +- [Test source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/tests/lib/rules/no-intl-durationformat.js) + +[no-new-in-esnext-intl-api]: ../configs/index.md#no-new-in-esnext-intl-api diff --git a/docs/rules/no-nonstandard-intl-durationformat-properties.md b/docs/rules/no-nonstandard-intl-durationformat-properties.md new file mode 100644 index 00000000..6f049dae --- /dev/null +++ b/docs/rules/no-nonstandard-intl-durationformat-properties.md @@ -0,0 +1,50 @@ +--- +title: "es-x/no-nonstandard-intl-durationformat-properties" +description: "disallow non-standard static properties on `Intl.DurationFormat` class" +--- + +# es-x/no-nonstandard-intl-durationformat-properties +> disallow non-standard static properties on `Intl.DurationFormat` class + +- ❗ ***This rule has not been released yet.*** + +This rule reports non-standard static properties on `Intl.DurationFormat` class as errors. + +## 💡 Examples + +⛔ Examples of **incorrect** code for this rule: + + + +```js +/*eslint es-x/no-nonstandard-intl-durationformat-properties: error */ +Intl.DurationFormat.unknown(); +``` + + + +## 🔧 Options + +This rule has an option. + +```jsonc +{ + "rules": { + "es-x/no-nonstandard-intl-durationformat-properties": [ + "error", + { + "allow": [] + } + ] + } +} +``` + +### allow: string[] + +An array of non-standard property names to allow. + +## 📚 References + +- [Rule source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/lib/rules/no-nonstandard-intl-durationformat-properties.js) +- [Test source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/tests/lib/rules/no-nonstandard-intl-durationformat-properties.js) diff --git a/docs/rules/no-nonstandard-intl-durationformat-prototype-properties.md b/docs/rules/no-nonstandard-intl-durationformat-prototype-properties.md new file mode 100644 index 00000000..f9e8e994 --- /dev/null +++ b/docs/rules/no-nonstandard-intl-durationformat-prototype-properties.md @@ -0,0 +1,51 @@ +--- +title: "es-x/no-nonstandard-intl-durationformat-prototype-properties" +description: "disallow non-standard properties on Intl.DurationFormat instance" +--- + +# es-x/no-nonstandard-intl-durationformat-prototype-properties +> disallow non-standard properties on Intl.DurationFormat instance + +- ❗ ***This rule has not been released yet.*** + +This rule reports non-standard properties on Intl.DurationFormat instance as errors. + +## 💡 Examples + +⛔ Examples of **incorrect** code for this rule: + + + +```js +/*eslint es-x/no-nonstandard-intl-durationformat-prototype-properties: error */ +const df = new Intl.DurationFormat("fr-FR", { style: "long" }); +df.unknown(); +``` + + + +## 🔧 Options + +This rule has an option. + +```jsonc +{ + "rules": { + "es-x/no-nonstandard-intl-durationformat-prototype-properties": [ + "error", + { + "allow": [] + } + ] + } +} +``` + +### allow: string[] + +An array of non-standard property names to allow. + +## 📚 References + +- [Rule source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/lib/rules/no-nonstandard-intl-durationformat-prototype-properties.js) +- [Test source](https://github.com/eslint-community/eslint-plugin-es-x/blob/master/tests/lib/rules/no-nonstandard-intl-durationformat-prototype-properties.js) diff --git a/lib/configs/flat/no-new-in-esnext-intl-api.js b/lib/configs/flat/no-new-in-esnext-intl-api.js index 270272d3..91a1feab 100644 --- a/lib/configs/flat/no-new-in-esnext-intl-api.js +++ b/lib/configs/flat/no-new-in-esnext-intl-api.js @@ -10,5 +10,5 @@ module.exports = { return require("../../index.js") }, }, - rules: {}, + rules: { "es-x/no-intl-durationformat": "error" }, } diff --git a/lib/configs/no-new-in-esnext-intl-api.js b/lib/configs/no-new-in-esnext-intl-api.js index f42c5997..71e35c13 100644 --- a/lib/configs/no-new-in-esnext-intl-api.js +++ b/lib/configs/no-new-in-esnext-intl-api.js @@ -4,4 +4,7 @@ */ "use strict" -module.exports = { plugins: ["es-x"], rules: {} } +module.exports = { + plugins: ["es-x"], + rules: { "es-x/no-intl-durationformat": "error" }, +} diff --git a/lib/index.js b/lib/index.js index f49eb1ae..aed64d3b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -213,6 +213,7 @@ module.exports = { "no-intl-datetimeformat-prototype-formatrange": require("./rules/no-intl-datetimeformat-prototype-formatrange"), "no-intl-datetimeformat-prototype-formattoparts": require("./rules/no-intl-datetimeformat-prototype-formattoparts"), "no-intl-displaynames": require("./rules/no-intl-displaynames"), + "no-intl-durationformat": require("./rules/no-intl-durationformat"), "no-intl-getcanonicallocales": require("./rules/no-intl-getcanonicallocales"), "no-intl-listformat": require("./rules/no-intl-listformat"), "no-intl-locale": require("./rules/no-intl-locale"), @@ -287,6 +288,8 @@ module.exports = { "no-nonstandard-intl-datetimeformat-prototype-properties": require("./rules/no-nonstandard-intl-datetimeformat-prototype-properties"), "no-nonstandard-intl-displaynames-properties": require("./rules/no-nonstandard-intl-displaynames-properties"), "no-nonstandard-intl-displaynames-prototype-properties": require("./rules/no-nonstandard-intl-displaynames-prototype-properties"), + "no-nonstandard-intl-durationformat-properties": require("./rules/no-nonstandard-intl-durationformat-properties"), + "no-nonstandard-intl-durationformat-prototype-properties": require("./rules/no-nonstandard-intl-durationformat-prototype-properties"), "no-nonstandard-intl-listformat-properties": require("./rules/no-nonstandard-intl-listformat-properties"), "no-nonstandard-intl-listformat-prototype-properties": require("./rules/no-nonstandard-intl-listformat-prototype-properties"), "no-nonstandard-intl-locale-properties": require("./rules/no-nonstandard-intl-locale-properties"), diff --git a/lib/rules/no-intl-durationformat.js b/lib/rules/no-intl-durationformat.js new file mode 100644 index 00000000..78348360 --- /dev/null +++ b/lib/rules/no-intl-durationformat.js @@ -0,0 +1,27 @@ +"use strict" + +const { + defineStaticPropertiesHandler, +} = require("../util/define-static-properties-handler") + +module.exports = { + meta: { + docs: { + description: "disallow the `Intl.DurationFormat` object.", + category: "ES2026-Intl-API", + recommended: false, + url: "http://eslint-community.github.io/eslint-plugin-es-x/rules/no-intl-durationformat.html", + }, + fixable: null, + messages: { + forbidden: "ES2026 Intl API '{{name}}' object is forbidden.", + }, + schema: [], + type: "problem", + }, + create(context) { + return defineStaticPropertiesHandler(context, { + Intl: ["DurationFormat"], + }) + }, +} diff --git a/lib/rules/no-nonstandard-intl-durationformat-properties.js b/lib/rules/no-nonstandard-intl-durationformat-properties.js new file mode 100644 index 00000000..ee991a5a --- /dev/null +++ b/lib/rules/no-nonstandard-intl-durationformat-properties.js @@ -0,0 +1,48 @@ +"use strict" + +const { + defineNonstandardStaticPropertiesHandler, +} = require("../util/define-nonstandard-static-properties-handler") +const { + intlDurationFormatProperties, +} = require("../util/well-known-properties") + +module.exports = { + meta: { + docs: { + description: + "disallow non-standard static properties on `Intl.DurationFormat` class", + category: "nonstandard", + recommended: false, + url: "http://eslint-community.github.io/eslint-plugin-es-x/rules/no-nonstandard-intl-durationformat-properties.html", + }, + fixable: null, + messages: { + forbidden: "Non-standard '{{name}}' property is forbidden.", + }, + schema: [ + { + type: "object", + properties: { + allow: { + type: "array", + items: { type: "string" }, + uniqueItems: true, + }, + }, + additionalProperties: false, + }, + ], + type: "problem", + }, + create(context) { + /** @type {Set} */ + const allows = new Set([ + ...(context.options[0]?.allow || []), + ...intlDurationFormatProperties, + ]) + return defineNonstandardStaticPropertiesHandler(context, { + "Intl.DurationFormat": allows, + }) + }, +} diff --git a/lib/rules/no-nonstandard-intl-durationformat-prototype-properties.js b/lib/rules/no-nonstandard-intl-durationformat-prototype-properties.js new file mode 100644 index 00000000..7769da6a --- /dev/null +++ b/lib/rules/no-nonstandard-intl-durationformat-prototype-properties.js @@ -0,0 +1,48 @@ +"use strict" + +const { + defineNonstandardPrototypePropertiesHandler, +} = require("../util/define-nonstandard-prototype-properties-handler") +const { + intlDurationFormatPrototypeProperties, +} = require("../util/well-known-properties") + +module.exports = { + meta: { + docs: { + description: + "disallow non-standard properties on Intl.DurationFormat instance", + category: "nonstandard", + recommended: false, + url: "http://eslint-community.github.io/eslint-plugin-es-x/rules/no-nonstandard-intl-durationformat-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, + }, + }, + additionalProperties: false, + }, + ], + type: "problem", + }, + create(context) { + /** @type {Set} */ + const allows = new Set([ + ...(context.options[0]?.allow || []), + ...intlDurationFormatPrototypeProperties, + ]) + return defineNonstandardPrototypePropertiesHandler(context, { + "Intl.DurationFormat": allows, + }) + }, +} diff --git a/lib/util/type-checker/es-types.js b/lib/util/type-checker/es-types.js index 98cc05ee..bd1c293e 100644 --- a/lib/util/type-checker/es-types.js +++ b/lib/util/type-checker/es-types.js @@ -335,6 +335,11 @@ const WELLKNOWN_GLOBALS = { return: { type: "Intl.DisplayNames" }, prototypeType: "Intl.DisplayNames", }, + DurationFormat: { + type: "Function", + return: { type: "Intl.DurationFormat" }, + prototypeType: "Intl.DurationFormat", + }, Locale: { type: "Function", return: { type: "Intl.Locale" }, diff --git a/lib/util/type-checker/types.d.ts b/lib/util/type-checker/types.d.ts index 151ac039..ed921be1 100644 --- a/lib/util/type-checker/types.d.ts +++ b/lib/util/type-checker/types.d.ts @@ -11,6 +11,7 @@ export type TypeName = | "Intl.Segmenter" | "Intl.DisplayNames" | "Intl.Locale" + | "Intl.DurationFormat" | "Promise" | "RegExp" | "String" @@ -193,7 +194,7 @@ export type FinalizationRegistryPrototypeProperty = Exclude< keyof FinalizationRegistry, ExcludePrototypeProperty >; -export type IntlProperty = Exclude; +export type IntlProperty = Exclude | 'DurationFormat'; export type IteratorProperty = Exclude; export type IteratorPrototypeProperty = Exclude< keyof IteratorObject, diff --git a/lib/util/well-known-properties.js b/lib/util/well-known-properties.js index 806d1752..c4e0db40 100644 --- a/lib/util/well-known-properties.js +++ b/lib/util/well-known-properties.js @@ -949,6 +949,7 @@ const intlProperties = new Set([ "Collator", "DateTimeFormat", "DisplayNames", + "DurationFormat", "ListFormat", "Locale", "NumberFormat", @@ -1022,6 +1023,26 @@ const intlDisplayNamesPrototypeProperties = new Set([ "resolvedOptions", ]) +const intlDurationFormatProperties = new Set([ + ...objectPrototypeProperties, + ...functionPrototypeProperties, + + // https://tc39.es/ecma402/#sec-properties-of-intl-durationformat-constructor + "prototype", + "supportedLocalesOf", +]) + +const intlDurationFormatPrototypeProperties = new Set([ + ...objectPrototypeProperties, + + // https://tc39.es/ecma402/#sec-properties-of-intl-durationformat-prototype-object + "constructor", + // [ %Symbol.toStringTag% ] + "format", + "formatToParts", + "resolvedOptions", +]) + const intlListFormatProperties = new Set([ ...objectPrototypeProperties, ...functionPrototypeProperties, @@ -1213,6 +1234,8 @@ module.exports = { intlDateTimeFormatPrototypeProperties, intlDisplayNamesProperties, intlDisplayNamesPrototypeProperties, + intlDurationFormatProperties, + intlDurationFormatPrototypeProperties, intlListFormatProperties, intlListFormatPrototypeProperties, intlLocaleProperties, diff --git a/package.json b/package.json index a79e151d..9fb10380 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "opener": "^1.5.1", "rimraf": "^6.0.0", "semver": "^7.0.0", - "typescript": "^5.6.3", + "typescript": "^5.7.2", "vite-plugin-eslint4b": "^0.5.0", "vitepress": "^1.0.0", "vue-eslint-parser": "^9.0.0" diff --git a/scripts/new-rule.js b/scripts/new-rule.js index 86c0b7fc..9de241ba 100644 --- a/scripts/new-rule.js +++ b/scripts/new-rule.js @@ -7,14 +7,10 @@ const cp = require("child_process") const fs = require("fs") const path = require("path") -const { categories } = require("./rules") +const { LATEST_ES_YEAR } = require("./rules") const logger = console -const maxESVersion = Math.max( - ...Object.keys(categories).map((esVersion) => - /^ES\d+$/u.test(esVersion) ? Number(esVersion.slice(2)) : 0, - ), -) +const maxESVersion = LATEST_ES_YEAR + 1 // main ;((ruleId) => { diff --git a/scripts/rules.js b/scripts/rules.js index 090942da..978331f9 100644 --- a/scripts/rules.js +++ b/scripts/rules.js @@ -41,7 +41,7 @@ const LATEST_ES_YEAR = 2024 /** @type {Record} */ const categories = [ ...(function* () { - const max = new Date().getFullYear() + 1 + const max = new Date().getFullYear() + 2 for (let year = max; year >= 2015; year--) { yield year } @@ -195,4 +195,4 @@ const rules = [] } })(libRoot) -module.exports = { categories, rules } +module.exports = { categories, rules, LATEST_ES_YEAR } diff --git a/tests/fixtures/tsconfig.json b/tests/fixtures/tsconfig.json index b843eb5a..2e6e7a54 100644 --- a/tests/fixtures/tsconfig.json +++ b/tests/fixtures/tsconfig.json @@ -3,5 +3,5 @@ "strict": true, "lib": ["ESNext"] }, - "include": ["test.ts"] + "include": ["test.ts", "types.d.ts"] } diff --git a/tests/fixtures/types.d.ts b/tests/fixtures/types.d.ts new file mode 100644 index 00000000..be6ce376 --- /dev/null +++ b/tests/fixtures/types.d.ts @@ -0,0 +1,174 @@ +// Copied from https://github.com/microsoft/TypeScript/pull/60646 +// and some modifications +declare namespace Intl { + /** + * Value of the `unit` property in duration objects + * + * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DurationFormat/format#duration). + */ + type DurationTimeFormatUnit = + | "years" + | "months" + | "weeks" + | "days" + | "hours" + | "minutes" + | "seconds" + | "milliseconds" + | "microseconds" + | "nanoseconds"; + + /** + * The style of the formatted duration. + * + * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DurationFormat/DurationFormat#style). + */ + type DurationFormatStyle = "long" | "short" | "narrow" | "digital"; + + type DurationFormatUnitSingular = + | "year" + | "quarter" + | "month" + | "week" + | "day" + | "hour" + | "minute" + | "second"; + + /** + * An object representing the relative time format in parts + * that can be used for custom locale-aware formatting. + * + * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DurationFormat/formatToParts#examples). + */ + type DurationFormatPart = + | { + type: "literal"; + value: string; + } + | { + type: Exclude; + value: string; + unit: DurationFormatUnitSingular; + }; + + type DurationFormatDisplayOption = "always" | "auto"; + + type FractionalDigitsOption = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; + + interface ResolvedDurationFormatOptions { + locale?: UnicodeBCP47LocaleIdentifier; + numberingSystem?: DateTimeFormatOptions["numberingSystem"]; + style?: DurationFormatStyle; + years?: "long" | "short" | "narrow"; + yearsDisplay?: DurationFormatDisplayOption; + months?: "long" | "short" | "narrow"; + monthsDisplay?: DurationFormatDisplayOption; + weeks?: "long" | "short" | "narrow"; + weeksDisplay?: DurationFormatDisplayOption; + days?: "long" | "short" | "narrow"; + daysDisplay?: DurationFormatDisplayOption; + hours?: "long" | "short" | "narrow" | "numeric" | "2-digit"; + hoursDisplay?: DurationFormatDisplayOption; + minutes?: "long" | "short" | "narrow" | "numeric" | "2-digit"; + minutesDisplay?: DurationFormatDisplayOption; + seconds?: "long" | "short" | "narrow" | "numeric" | "2-digit"; + secondsDisplay?: DurationFormatDisplayOption; + milliseconds?: "long" | "short" | "narrow" | "fractional"; + millisecondsDisplay?: DurationFormatDisplayOption; + microseconds?: "long" | "short" | "narrow" | "fractional"; + microsecondsDisplay?: DurationFormatDisplayOption; + nanosecond?: "long" | "short" | "narrow" | "fractional"; + nanosecondDisplay?: DurationFormatDisplayOption; + fractionalDigits?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; + } + + interface DurationFormatOptions { + localeMatcher?: RelativeTimeFormatLocaleMatcher; + numberingSystem?: DateTimeFormatOptions["numberingSystem"]; + style?: DurationFormatStyle; + years?: "long" | "short" | "narrow"; + yearsDisplay?: DurationFormatDisplayOption; + months?: "long" | "short" | "narrow"; + monthsDisplay?: DurationFormatDisplayOption; + weeks?: "long" | "short" | "narrow"; + weeksDisplay?: DurationFormatDisplayOption; + days?: "long" | "short" | "narrow"; + daysDisplay?: DurationFormatDisplayOption; + hours?: "long" | "short" | "narrow" | "numeric" | "2-digit"; + hoursDisplay?: DurationFormatDisplayOption; + minutes?: "long" | "short" | "narrow" | "numeric" | "2-digit"; + minutesDisplay?: DurationFormatDisplayOption; + seconds?: "long" | "short" | "narrow" | "numeric" | "2-digit"; + secondsDisplay?: DurationFormatDisplayOption; + milliseconds?: "long" | "short" | "narrow" | "fractional"; + millisecondsDisplay?: DurationFormatDisplayOption; + microseconds?: "long" | "short" | "narrow" | "fractional"; + microsecondsDisplay?: DurationFormatDisplayOption; + nanosecond?: "long" | "short" | "narrow" | "fractional"; + nanosecondDisplay?: DurationFormatDisplayOption; + fractionalDigits?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; + } + + /** + * The duration object to be formatted + * + * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DurationFormat/format#duration). + */ + type DurationType = Record; + + interface DurationFormat { + /** + * @param duration The duration object to be formatted. It should include some or all of the following properties: months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds. + * + * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DurationFormat/format). + */ + format(duration: DurationType): string; + /** + * @param duration The duration object to be formatted. It should include some or all of the following properties: months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds. + * + * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DurationFormat/formatToParts). + */ + formatToParts(duration: DurationType): DurationFormatPart[]; + /** + * [MDN](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Intl/DurationFormat/resolvedOptions). + */ + resolvedOptions(): ResolvedDurationFormatOptions; + } + + const DurationFormat: { + prototype: DurationFormat; + + /** + * @param locales A string with a BCP 47 language tag, or an array of such strings. + * For the general form and interpretation of the `locales` argument, see the [Intl](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Intl#locale_identification_and_negotiation) + * page. + * + * @param options An object for setting up a duration format. + * + * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DurationFormat/DurationFormat). + */ + new ( + locales: LocalesArgument, + options: DurationFormatOptions, + ): DurationFormat; + + /** + * Returns an array containing those of the provided locales that are supported in display names without having to fall back to the runtime's default locale. + * + * @param locales A string with a BCP 47 language tag, or an array of such strings. + * For the general form and interpretation of the `locales` argument, see the [Intl](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Intl#locale_identification_and_negotiation) + * page. + * + * @param options An object with a locale matcher. + * + * @returns An array of strings representing a subset of the given locale tags that are supported in display names without having to fall back to the runtime's default locale. + * + * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DurationFormat/supportedLocalesOf). + */ + supportedLocalesOf( + locales?: LocalesArgument, + options?: { localeMatcher?: RelativeTimeFormatLocaleMatcher }, + ): UnicodeBCP47LocaleIdentifier[]; + }; +} diff --git a/tests/lib/rules/no-intl-durationformat.js b/tests/lib/rules/no-intl-durationformat.js new file mode 100644 index 00000000..50489d80 --- /dev/null +++ b/tests/lib/rules/no-intl-durationformat.js @@ -0,0 +1,21 @@ +"use strict" + +const RuleTester = require("../../tester") +const rule = require("../../../lib/rules/no-intl-durationformat") + +new RuleTester().run("no-intl-durationformat", rule, { + valid: [ + "Intl", + "Intl.DateTimeFormat", + "Intl.NumberFormat", + "let Intl = 0; Intl.DurationFormat", + ], + invalid: [ + { + code: "Intl.DurationFormat", + errors: [ + "ES2026 Intl API 'Intl.DurationFormat' object is forbidden.", + ], + }, + ], +}) diff --git a/tests/lib/rules/no-nonstandard-intl-durationformat-properties.js b/tests/lib/rules/no-nonstandard-intl-durationformat-properties.js new file mode 100644 index 00000000..099930a1 --- /dev/null +++ b/tests/lib/rules/no-nonstandard-intl-durationformat-properties.js @@ -0,0 +1,39 @@ +"use strict" + +const RuleTester = require("../../tester") +const rule = require("../../../lib/rules/no-nonstandard-intl-durationformat-properties.js") +const { + intlDurationFormatProperties, +} = require("../../../lib/util/well-known-properties") + +new RuleTester().run("no-nonstandard-intl-durationformat-properties", rule, { + valid: [ + ...[...intlDurationFormatProperties].map( + (p) => `Intl.DurationFormat.${p}`, + ), + { + code: "Intl.DurationFormat.unknown()", + options: [{ allow: ["unknown"] }], + }, + ], + invalid: [ + { + code: "Intl.DurationFormat.unknown()", + errors: [ + "Non-standard 'Intl.DurationFormat.unknown' property is forbidden.", + ], + }, + { + code: "Intl.DurationFormat.foo", + errors: [ + "Non-standard 'Intl.DurationFormat.foo' property is forbidden.", + ], + }, + { + code: "Intl.DurationFormat.bar", + errors: [ + "Non-standard 'Intl.DurationFormat.bar' property is forbidden.", + ], + }, + ], +}) diff --git a/tests/lib/rules/no-nonstandard-intl-durationformat-prototype-properties.js b/tests/lib/rules/no-nonstandard-intl-durationformat-prototype-properties.js new file mode 100644 index 00000000..cc804359 --- /dev/null +++ b/tests/lib/rules/no-nonstandard-intl-durationformat-prototype-properties.js @@ -0,0 +1,143 @@ +"use strict" + +const path = require("path") +const RuleTester = require("../../tester") +const rule = require("../../../lib/rules/no-nonstandard-intl-durationformat-prototype-properties.js") +const { + intlDurationFormatPrototypeProperties, +} = require("../../../lib/util/well-known-properties") +const ruleId = "no-nonstandard-intl-durationformat-prototype-properties" + +new RuleTester().run(ruleId, rule, { + valid: [ + "foo", + "foo.toString", + "foo.foo", + ...[...intlDurationFormatPrototypeProperties].map( + (p) => `new Intl.DurationFormat().${p}`, + ), + { + code: "new Intl.DurationFormat().unknown()", + options: [{ allow: ["unknown"] }], + }, + ], + invalid: [ + { + code: "new Intl.DurationFormat().unknown()", + errors: [ + "Non-standard 'Intl.DurationFormat.prototype.unknown' property is forbidden.", + ], + }, + { + code: "new Intl.DurationFormat().foo", + errors: [ + "Non-standard 'Intl.DurationFormat.prototype.foo' property is forbidden.", + ], + }, + { + code: "new Intl.DurationFormat().bar", + errors: [ + "Non-standard 'Intl.DurationFormat.prototype.bar' property is forbidden.", + ], + }, + { + code: "new Intl.DurationFormat()[0]", + errors: [ + "Non-standard 'Intl.DurationFormat.prototype.0' property is forbidden.", + ], + }, + { + code: "new Intl.DurationFormat()['0']", + errors: [ + "Non-standard 'Intl.DurationFormat.prototype.0' property is forbidden.", + ], + }, + { + code: "new Intl.DurationFormat()['01']", + errors: [ + "Non-standard 'Intl.DurationFormat.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" }, + ...[...intlDurationFormatPrototypeProperties].map((p) => ({ + filename, + code: `new Intl.DurationFormat().${p}`, + })), + ], + invalid: [ + { + filename, + code: "new Intl.DurationFormat().foo", + errors: [ + "Non-standard 'Intl.DurationFormat.prototype.foo' property is forbidden.", + ], + }, + { + filename, + code: "new Intl.DurationFormat().bar", + errors: [ + "Non-standard 'Intl.DurationFormat.prototype.bar' property is forbidden.", + ], + }, + { + filename, + code: "new Intl.DurationFormat()[0]", + errors: [ + "Non-standard 'Intl.DurationFormat.prototype.0' property is forbidden.", + ], + }, + { + filename, + code: "new Intl.DurationFormat()['0']", + errors: [ + "Non-standard 'Intl.DurationFormat.prototype.0' property is forbidden.", + ], + }, + { + filename, + code: "new Intl.DurationFormat()['01']", + errors: [ + "Non-standard 'Intl.DurationFormat.prototype.01' property is forbidden.", + ], + }, + { + filename, + code: "let foo = new Intl.DurationFormat(); foo.foo", + errors: [ + "Non-standard 'Intl.DurationFormat.prototype.foo' property is forbidden.", + ], + }, + { + filename, + code: "function f(a: T) { a.baz }", + errors: [ + "Non-standard 'Intl.DurationFormat.prototype.baz' property is forbidden.", + ], + }, + ], +}) From 80d9f5597e0b5e7e8bf08982c834658e4de451a0 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Tue, 14 Jan 2025 23:09:28 +0900 Subject: [PATCH 2/2] update --- docs/rules/index.md | 18 +++++++++--------- docs/rules/no-intl-durationformat.md | 2 +- lib/rules/no-intl-durationformat.js | 4 ++-- tests/lib/rules/no-intl-durationformat.js | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/rules/index.md b/docs/rules/index.md index b1ca2001..c5d0f97e 100644 --- a/docs/rules/index.md +++ b/docs/rules/index.md @@ -4,14 +4,6 @@ This plugin provides the following rules. - 🔧 mark means that the `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by the rule. -## ES2026 Intl API - -There is a config that enables the rules in this category: [`no-new-in-esnext-intl-api`] - -| Rule ID | Description | | -|:--------|:------------|:--:| -| [es-x/no-intl-durationformat](./no-intl-durationformat.md) | disallow the `Intl.DurationFormat` object. | | - ## ES2025 There is a config that enables the rules in this category: [`no-new-in-esnext`] @@ -45,6 +37,14 @@ There is a config that enables the rules in this category: [`no-new-in-esnext`] | [es-x/no-set-prototype-union](./no-set-prototype-union.md) | disallow the `Set.prototype.union` method. | | | [es-x/no-trailing-dynamic-import-commas](./no-trailing-dynamic-import-commas.md) | disallow trailing commas in `import()`. | 🔧 | +## ES2025 Intl API + +There is a config that enables the rules in this category: [`no-new-in-esnext-intl-api`] + +| Rule ID | Description | | +|:--------|:------------|:--:| +| [es-x/no-intl-durationformat](./no-intl-durationformat.md) | disallow the `Intl.DurationFormat` object. | | + ## ES2024 There are multiple configs that enable all rules in this category: [`no-new-in-es2024`], [`restrict-to-es3`], [`restrict-to-es5`], [`restrict-to-es2015`], [`restrict-to-es2016`], [`restrict-to-es2017`], [`restrict-to-es2018`], [`restrict-to-es2019`], [`restrict-to-es2020`], [`restrict-to-es2021`], [`restrict-to-es2022`], and [`restrict-to-es2023`] @@ -472,8 +472,8 @@ Rules in this category are not included in any preset. | [es-x/no-object-map-groupby](./no-object-map-groupby.md) | [es-x/no-object-groupby](./no-object-groupby.md), [es-x/no-map-groupby](./no-map-groupby.md) | | [es-x/no-string-prototype-iswellformed-towellformed](./no-string-prototype-iswellformed-towellformed.md) | [es-x/no-string-prototype-iswellformed](./no-string-prototype-iswellformed.md), [es-x/no-string-prototype-towellformed](./no-string-prototype-towellformed.md) | -[`no-new-in-esnext-intl-api`]: ../configs/index.md#no-new-in-esnext-intl-api [`no-new-in-esnext`]: ../configs/index.md#no-new-in-esnext +[`no-new-in-esnext-intl-api`]: ../configs/index.md#no-new-in-esnext-intl-api [`no-new-in-es2024`]: ../configs/index.md#no-new-in-es2024 [`restrict-to-es2023`]: ../configs/index.md#restrict-to-es2023 [`restrict-to-es2023-intl-api`]: ../configs/index.md#restrict-to-es2023-intl-api diff --git a/docs/rules/no-intl-durationformat.md b/docs/rules/no-intl-durationformat.md index 77340328..d0691803 100644 --- a/docs/rules/no-intl-durationformat.md +++ b/docs/rules/no-intl-durationformat.md @@ -9,7 +9,7 @@ description: "disallow the `Intl.DurationFormat` object" - ❗ ***This rule has not been released yet.*** - ✅ The following configurations enable this rule: [no-new-in-esnext-intl-api] -This rule reports ES2026 Intl API `Intl.DurationFormat` object as errors. +This rule reports ES2025 Intl API `Intl.DurationFormat` object as errors. ## 💡 Examples diff --git a/lib/rules/no-intl-durationformat.js b/lib/rules/no-intl-durationformat.js index 78348360..ffe8d888 100644 --- a/lib/rules/no-intl-durationformat.js +++ b/lib/rules/no-intl-durationformat.js @@ -8,13 +8,13 @@ module.exports = { meta: { docs: { description: "disallow the `Intl.DurationFormat` object.", - category: "ES2026-Intl-API", + category: "ES2025-Intl-API", recommended: false, url: "http://eslint-community.github.io/eslint-plugin-es-x/rules/no-intl-durationformat.html", }, fixable: null, messages: { - forbidden: "ES2026 Intl API '{{name}}' object is forbidden.", + forbidden: "ES2025 Intl API '{{name}}' object is forbidden.", }, schema: [], type: "problem", diff --git a/tests/lib/rules/no-intl-durationformat.js b/tests/lib/rules/no-intl-durationformat.js index 50489d80..5b845ced 100644 --- a/tests/lib/rules/no-intl-durationformat.js +++ b/tests/lib/rules/no-intl-durationformat.js @@ -14,7 +14,7 @@ new RuleTester().run("no-intl-durationformat", rule, { { code: "Intl.DurationFormat", errors: [ - "ES2026 Intl API 'Intl.DurationFormat' object is forbidden.", + "ES2025 Intl API 'Intl.DurationFormat' object is forbidden.", ], }, ],