diff --git a/apps/oxfmt/src-js/index.ts b/apps/oxfmt/src-js/index.ts index 94ed6950ea859..6fc4d0a074069 100644 --- a/apps/oxfmt/src-js/index.ts +++ b/apps/oxfmt/src-js/index.ts @@ -127,7 +127,12 @@ export type SortImportsOptions = { */ groups?: (string | string[])[]; /** Define custom groups for matching specific imports. */ - customGroups?: { groupName: string; elementNamePattern: string[] }[]; + customGroups?: { + groupName: string; + elementNamePattern?: string[]; + selector?: string; + modifiers?: string[]; + }[]; }; /** diff --git a/apps/oxfmt/src/core/oxfmtrc.rs b/apps/oxfmt/src/core/oxfmtrc.rs index 89a9eb3bbdf88..b114be2f5e83d 100644 --- a/apps/oxfmt/src/core/oxfmtrc.rs +++ b/apps/oxfmt/src/core/oxfmtrc.rs @@ -6,9 +6,9 @@ use serde_json::Value; use oxc_formatter::{ ArrowParentheses, AttributePosition, BracketSameLine, BracketSpacing, CustomGroupDefinition, - EmbeddedLanguageFormatting, Expand, FormatOptions, IndentStyle, IndentWidth, LineEnding, - LineWidth, QuoteProperties, QuoteStyle, Semicolons, SortImportsOptions, SortOrder, - TailwindcssOptions, TrailingCommas, + EmbeddedLanguageFormatting, Expand, FormatOptions, ImportModifier, ImportSelector, IndentStyle, + IndentWidth, LineEnding, LineWidth, QuoteProperties, QuoteStyle, Semicolons, + SortImportsOptions, SortOrder, TailwindcssOptions, TrailingCommas, }; use oxc_toml::Options as TomlFormatterOptions; @@ -428,6 +428,13 @@ impl FormatConfig { .map(|c| CustomGroupDefinition { group_name: c.group_name, element_name_pattern: c.element_name_pattern, + selector: c.selector.as_deref().and_then(ImportSelector::parse), + modifiers: c + .modifiers + .unwrap_or_default() + .iter() + .filter_map(|s| ImportModifier::parse(s)) + .collect(), }) .collect(); } @@ -650,10 +657,6 @@ pub struct SortImportsConfig { /// - `default` — Imports containing the default specifier. /// - `wildcard` — Imports containing the wildcard (`* as`) specifier. /// - `named` — Imports containing at least one named specifier. - /// - `multiline` — Imports on multiple lines. - /// - `singleline` — Imports on a single line. - /// - /// See also for details. /// /// - Default: See below /// ```json @@ -677,6 +680,9 @@ pub struct SortImportsConfig { /// If you want a predefined group to take precedence over a custom group, /// you must write a custom group definition that does the same as what the predefined group does, and put it first in the list. /// + /// If you specify multiple conditions like `elementNamePattern`, `selector`, and `modifiers`, + /// all conditions must be met for an import to match the custom group (AND logic). + /// /// - Default: `[]` #[serde(skip_serializing_if = "Option::is_none")] pub custom_groups: Option>, @@ -712,6 +718,18 @@ pub struct CustomGroupItemConfig { pub group_name: String, /// List of glob patterns to match import sources for this group. pub element_name_pattern: Vec, + /// Selector to match the import kind. + /// + /// Possible values: `"type"`, `"side-effect-style"`, `"side-effect"`, `"style"`, `"index"`, + /// `"sibling"`, `"parent"`, `"subpath"`, `"internal"`, `"builtin"`, `"external"`, `"import"` + #[serde(skip_serializing_if = "Option::is_none")] + pub selector: Option, + /// Modifiers to match the import characteristics. + /// All specified modifiers must be present (AND logic). + /// + /// Possible values: `"side-effect"`, `"type"`, `"value"`, `"default"`, `"wildcard"`, `"named"` + #[serde(skip_serializing_if = "Option::is_none")] + pub modifiers: Option>, } // --- diff --git a/apps/oxfmt/test/api/sort_imports.test.ts b/apps/oxfmt/test/api/sort_imports.test.ts index 3c00b8fb5530f..e7909753b445d 100644 --- a/apps/oxfmt/test/api/sort_imports.test.ts +++ b/apps/oxfmt/test/api/sort_imports.test.ts @@ -23,6 +23,42 @@ import { store } from "~/stores/store"; import { store } from "~/stores/store"; import { util } from "~/utils/util"; import { foo } from "./foo"; +`.trimStart(), + ); + expect(result.errors).toStrictEqual([]); + }); + + it("should sort with customGroups using selector and modifiers", async () => { + const input = `import { bar } from "@scope/bar"; +import type { FooType } from "@scope/foo"; +import { foo } from "@scope/foo"; +import type { BarType } from "@scope/bar"; +`; + const result = await format("a.ts", input, { + experimentalSortImports: { + customGroups: [ + { + groupName: "scope-types", + elementNamePattern: ["@scope/**"], + modifiers: ["type"], + }, + { + groupName: "scope-values", + elementNamePattern: ["@scope/**"], + modifiers: ["value"], + }, + ], + groups: ["scope-types", "scope-values", "unknown"], + }, + }); + + expect(result.code).toBe( + ` +import type { BarType } from "@scope/bar"; +import type { FooType } from "@scope/foo"; + +import { bar } from "@scope/bar"; +import { foo } from "@scope/foo"; `.trimStart(), ); expect(result.errors).toStrictEqual([]); diff --git a/apps/oxfmt/test/cli/sort_imports/fixtures/custom_groups_selector_modifiers/.oxfmtrc.json b/apps/oxfmt/test/cli/sort_imports/fixtures/custom_groups_selector_modifiers/.oxfmtrc.json new file mode 100644 index 0000000000000..014d1f38e97b6 --- /dev/null +++ b/apps/oxfmt/test/cli/sort_imports/fixtures/custom_groups_selector_modifiers/.oxfmtrc.json @@ -0,0 +1,21 @@ +{ + "experimentalSortImports": { + "customGroups": [ + { + "groupName": "scope-types", + "elementNamePattern": ["@scope/**"], + "modifiers": ["type"] + }, + { + "groupName": "scope-values", + "elementNamePattern": ["@scope/**"], + "modifiers": ["value"] + }, + { + "groupName": "externals", + "selector": "external" + } + ], + "groups": ["scope-types", "scope-values", "externals", "unknown"] + } +} diff --git a/apps/oxfmt/test/cli/sort_imports/fixtures/custom_groups_selector_modifiers/input.ts b/apps/oxfmt/test/cli/sort_imports/fixtures/custom_groups_selector_modifiers/input.ts new file mode 100644 index 0000000000000..df5af5252198a --- /dev/null +++ b/apps/oxfmt/test/cli/sort_imports/fixtures/custom_groups_selector_modifiers/input.ts @@ -0,0 +1,9 @@ +import type { BarType } from "@scope/bar"; +import type { FooType } from "@scope/foo"; + +import { bar } from "@scope/bar"; +import { foo } from "@scope/foo"; + +import { ext } from "external-lib"; + +import { sibling } from "./sibling"; diff --git a/apps/oxfmt/test/cli/sort_imports/sort_imports.test.ts b/apps/oxfmt/test/cli/sort_imports/sort_imports.test.ts index 32e39fced54a7..a88d42b1ac548 100644 --- a/apps/oxfmt/test/cli/sort_imports/sort_imports.test.ts +++ b/apps/oxfmt/test/cli/sort_imports/sort_imports.test.ts @@ -11,4 +11,11 @@ describe("sort_imports", () => { expect(result.exitCode).toBe(0); }); + + it("should sort imports with customGroups using selector and modifiers", async () => { + const cwd = join(fixturesDir, "custom_groups_selector_modifiers"); + const result = await runCli(cwd, ["--check", "input.ts"]); + + expect(result.exitCode).toBe(0); + }); }); diff --git a/crates/oxc_formatter/src/ir_transform/sort_imports/group_config.rs b/crates/oxc_formatter/src/ir_transform/sort_imports/group_config.rs index aad7a90321268..6e933aa0720d5 100644 --- a/crates/oxc_formatter/src/ir_transform/sort_imports/group_config.rs +++ b/crates/oxc_formatter/src/ir_transform/sort_imports/group_config.rs @@ -197,6 +197,19 @@ impl ImportModifier { ImportModifier::Named, ]; + /// Parse a string into an ImportModifier. + pub fn parse(s: &str) -> Option { + match s { + "side-effect" => Some(Self::SideEffect), + "type" => Some(Self::Type), + "value" => Some(Self::Value), + "default" => Some(Self::Default), + "wildcard" => Some(Self::Wildcard), + "named" => Some(Self::Named), + _ => None, + } + } + pub fn name(&self) -> &str { match self { ImportModifier::SideEffect => "side-effect", diff --git a/crates/oxc_formatter/src/ir_transform/sort_imports/group_matcher.rs b/crates/oxc_formatter/src/ir_transform/sort_imports/group_matcher.rs index d00377258372c..07f03eeebf70b 100644 --- a/crates/oxc_formatter/src/ir_transform/sort_imports/group_matcher.rs +++ b/crates/oxc_formatter/src/ir_transform/sort_imports/group_matcher.rs @@ -13,11 +13,9 @@ pub struct ImportMetadata<'a> { pub struct GroupMatcher { // Custom groups that are used in `options.groups` custom_groups: Vec<(CustomGroupDefinition, usize)>, - // Predefined groups sorted by priority, // so that we don't need to enumerate all possible group names of a given import. predefined_groups: Vec<(GroupName, usize)>, - // The index of "unknown" in groups or `groups.len()` if absent unknown_group_index: usize, } @@ -62,10 +60,21 @@ impl GroupMatcher { pub fn compute_group_index(&self, import_metadata: &ImportMetadata) -> usize { for (custom_group, index) in &self.custom_groups { - let is_match = custom_group - .element_name_pattern - .iter() - .any(|pattern| fast_glob::glob_match(pattern, import_metadata.source)); + let is_match = { + let name_matches = custom_group.element_name_pattern.is_empty() + || custom_group + .element_name_pattern + .iter() + .any(|pattern| fast_glob::glob_match(pattern, import_metadata.source)); + let selector_matches = + custom_group.selector.is_none_or(|s| import_metadata.selectors.contains(&s)); + let modifiers_match = + custom_group.modifiers.iter().all(|m| import_metadata.modifiers.contains(m)); + + // These are AND logic + name_matches && selector_matches && modifiers_match + }; + if is_match { return *index; } diff --git a/crates/oxc_formatter/src/ir_transform/sort_imports/options.rs b/crates/oxc_formatter/src/ir_transform/sort_imports/options.rs index c6f5ce2b85369..8f429eb969157 100644 --- a/crates/oxc_formatter/src/ir_transform/sort_imports/options.rs +++ b/crates/oxc_formatter/src/ir_transform/sort_imports/options.rs @@ -1,6 +1,8 @@ use std::fmt; use std::str::FromStr; +pub use super::group_config::{ImportModifier, ImportSelector}; + #[derive(Clone, Debug, Eq, PartialEq)] pub struct SortImportsOptions { /// Partition imports by newlines. @@ -101,6 +103,10 @@ pub struct CustomGroupDefinition { pub group_name: String, /// List of glob patterns to match import sources for this group. pub element_name_pattern: Vec, + /// When specified, the import's selectors must contain this selector. + pub selector: Option, + /// When specified, **all** modifiers must be present in the import's modifiers (AND logic). + pub modifiers: Vec, } /// Returns default prefixes for identifying internal imports: `["~/", "@/"]`. diff --git a/crates/oxc_formatter/tests/ir_transform/mod.rs b/crates/oxc_formatter/tests/ir_transform/mod.rs index 3e6f9ba9518f8..996d7942a2cbf 100644 --- a/crates/oxc_formatter/tests/ir_transform/mod.rs +++ b/crates/oxc_formatter/tests/ir_transform/mod.rs @@ -1,7 +1,8 @@ mod sort_imports; use oxc_formatter::{ - CustomGroupDefinition, FormatOptions, QuoteStyle, Semicolons, SortImportsOptions, SortOrder, + CustomGroupDefinition, FormatOptions, ImportModifier, ImportSelector, QuoteStyle, Semicolons, + SortImportsOptions, SortOrder, }; use serde::Deserialize; @@ -60,7 +61,10 @@ struct TestConfig { #[serde(rename_all = "camelCase")] struct TestCustomGroupDefinition { group_name: String, + #[serde(default)] element_name_pattern: Vec, + selector: Option, + modifiers: Option>, } #[derive(Debug, Default, Deserialize)] @@ -158,6 +162,13 @@ fn parse_test_config(json: &str) -> FormatOptions { .map(|value| CustomGroupDefinition { group_name: value.group_name, element_name_pattern: value.element_name_pattern, + selector: value.selector.as_deref().and_then(ImportSelector::parse), + modifiers: value + .modifiers + .unwrap_or_default() + .iter() + .filter_map(|s| ImportModifier::parse(s)) + .collect(), }) .collect(); } diff --git a/crates/oxc_formatter/tests/ir_transform/sort_imports/custom_groups.rs b/crates/oxc_formatter/tests/ir_transform/sort_imports/custom_groups.rs index e76bf70e837b0..d4fac97320c98 100644 --- a/crates/oxc_formatter/tests/ir_transform/sort_imports/custom_groups.rs +++ b/crates/oxc_formatter/tests/ir_transform/sort_imports/custom_groups.rs @@ -381,3 +381,438 @@ import Vuetify from "vuetify"; "#, ); } + +#[test] +fn custom_group_with_selector_only() { + // Custom group matching by selector only (no elementNamePattern) + assert_format( + r#" +import { foo } from "foo"; +import type { Bar } from "bar"; +import { baz } from "baz"; +import type { Qux } from "qux"; +"#, + r#" +{ + "experimentalSortImports": { + "customGroups": [ + { + "groupName": "types", + "selector": "type" + } + ], + "groups": [ + "types", + "unknown" + ] + } +} +"#, + r#" +import type { Bar } from "bar"; +import type { Qux } from "qux"; + +import { baz } from "baz"; +import { foo } from "foo"; +"#, + ); +} + +#[test] +fn custom_group_with_modifiers_only() { + // Custom group matching by modifiers only (no elementNamePattern) + assert_format( + r#" +import { foo } from "foo"; +import type { Bar } from "bar"; +import { baz } from "baz"; +import type { Qux } from "qux"; +"#, + r#" +{ + "experimentalSortImports": { + "customGroups": [ + { + "groupName": "type-imports", + "modifiers": ["type"] + } + ], + "groups": [ + "type-imports", + "unknown" + ] + } +} +"#, + r#" +import type { Bar } from "bar"; +import type { Qux } from "qux"; + +import { baz } from "baz"; +import { foo } from "foo"; +"#, + ); +} + +#[test] +fn custom_group_with_selector_and_pattern() { + // Custom group matching by selector + elementNamePattern + assert_format( + r#" +import type { InternalType } from "~/types"; +import type { ExternalType } from "ext-lib"; +import { internalUtil } from "~/utils"; +import { externalUtil } from "ext-lib"; +"#, + r#" +{ + "experimentalSortImports": { + "customGroups": [ + { + "groupName": "internal-types", + "selector": "internal", + "elementNamePattern": ["~/**"] + } + ], + "groups": [ + "internal-types", + "unknown" + ], + "internalPattern": ["~/"] + } +} +"#, + r#" +import type { InternalType } from "~/types"; +import { internalUtil } from "~/utils"; + +import type { ExternalType } from "ext-lib"; +import { externalUtil } from "ext-lib"; +"#, + ); +} + +#[test] +fn custom_group_with_selector_modifiers_and_pattern() { + // Custom group matching by selector + modifiers + elementNamePattern (all AND) + assert_format( + r#" +import type { InternalType } from "~/types"; +import type { ExternalType } from "ext-lib"; +import { internalUtil } from "~/utils"; +import { externalUtil } from "ext-lib"; +"#, + r#" +{ + "experimentalSortImports": { + "customGroups": [ + { + "groupName": "internal-type-imports", + "selector": "internal", + "modifiers": ["type"], + "elementNamePattern": ["~/**"] + } + ], + "groups": [ + "internal-type-imports", + "unknown" + ], + "internalPattern": ["~/"] + } +} +"#, + r#" +import type { InternalType } from "~/types"; + +import type { ExternalType } from "ext-lib"; +import { externalUtil } from "ext-lib"; +import { internalUtil } from "~/utils"; +"#, + ); +} + +#[test] +fn custom_group_no_match_falls_to_unknown() { + // When selector doesn't match, import falls to unknown + assert_format( + r#" +import { foo } from "foo"; +import { bar } from "bar"; +"#, + r#" +{ + "experimentalSortImports": { + "customGroups": [ + { + "groupName": "types-only", + "selector": "type" + } + ], + "groups": [ + "types-only", + "unknown" + ] + } +} +"#, + r#" +import { bar } from "bar"; +import { foo } from "foo"; +"#, + ); +} + +#[test] +fn custom_group_multiple_modifiers_and_logic() { + // All specified modifiers must match (AND logic) + // "type" + "named" matches only `import type { ... }`, not `import type X` (default only) + assert_format( + r#" +import type Bar from "bar"; +import type { Foo } from "foo"; +import { regular } from "regular"; +"#, + r#" +{ + "experimentalSortImports": { + "customGroups": [ + { + "groupName": "type-named", + "modifiers": ["type", "named"] + } + ], + "groups": [ + "type-named", + "unknown" + ] + } +} +"#, + r#" +import type { Foo } from "foo"; + +import type Bar from "bar"; +import { regular } from "regular"; +"#, + ); +} + +#[test] +fn selector_external_groups_external_imports() { + // selector: "external" separates external from sibling imports + assert_format( + r#" +import a from "a"; +import b from "./b"; +import c from "c"; +import d from "./d"; +import e from "e"; +"#, + r#" +{ + "experimentalSortImports": { + "customGroups": [ + { + "groupName": "externalImports", + "selector": "external" + } + ], + "groups": [ + "externalImports", + "unknown" + ] + } +} +"#, + r#" +import a from "a"; +import c from "c"; +import e from "e"; + +import b from "./b"; +import d from "./d"; +"#, + ); +} + +#[test] +fn custom_groups_with_predefined_type_group() { + // Custom groups take priority over predefined groups. + // `import type { T } from "t"` matches custom "primary" (pattern "t") before predefined "type". + assert_format( + r#" +import type { T } from "t"; +import a1 from "@a/a1"; +import a2 from "@a/a2"; +import b1 from "@b/b1"; +import b2 from "@b/b2"; +import b3 from "@b/b3"; +import { c } from "c"; +"#, + r#" +{ + "experimentalSortImports": { + "customGroups": [ + { + "groupName": "primary", + "elementNamePattern": ["t", "@a/**"] + }, + { + "groupName": "secondary", + "elementNamePattern": ["@b/**"], + "modifiers": ["value"] + } + ], + "groups": [ + "type", + "primary", + "secondary", + "unknown" + ] + } +} +"#, + r#" +import a1 from "@a/a1"; +import a2 from "@a/a2"; +import type { T } from "t"; + +import b1 from "@b/b1"; +import b2 from "@b/b2"; +import b3 from "@b/b3"; + +import { c } from "c"; +"#, + ); +} + +#[test] +fn multiple_custom_groups_with_different_selectors() { + // Each custom group uses a different selector to categorize imports + assert_format( + r#" +import fs from "node:fs"; +import { foo } from "external-lib"; +import { bar } from "~/internal"; +import { baz } from "./sibling"; +"#, + r#" +{ + "experimentalSortImports": { + "internalPattern": ["~/"], + "customGroups": [ + { + "groupName": "builtins", + "selector": "builtin" + }, + { + "groupName": "externals", + "selector": "external" + }, + { + "groupName": "internals", + "selector": "internal" + } + ], + "groups": [ + "builtins", + "externals", + "internals", + "unknown" + ] + } +} +"#, + r#" +import fs from "node:fs"; + +import { foo } from "external-lib"; + +import { bar } from "~/internal"; + +import { baz } from "./sibling"; +"#, + ); +} + +#[test] +fn same_pattern_differentiated_by_modifiers() { + // Same elementNamePattern used in two custom groups, differentiated by type vs value modifiers + assert_format( + r#" +import type { FooType } from "@scope/foo"; +import { foo } from "@scope/foo"; +import type { BarType } from "@scope/bar"; +import { bar } from "@scope/bar"; +"#, + r#" +{ + "experimentalSortImports": { + "customGroups": [ + { + "groupName": "scope-types", + "elementNamePattern": ["@scope/**"], + "modifiers": ["type"] + }, + { + "groupName": "scope-values", + "elementNamePattern": ["@scope/**"], + "modifiers": ["value"] + } + ], + "groups": [ + "scope-types", + "scope-values", + "unknown" + ] + } +} +"#, + r#" +import type { BarType } from "@scope/bar"; +import type { FooType } from "@scope/foo"; + +import { bar } from "@scope/bar"; +import { foo } from "@scope/foo"; +"#, + ); +} + +#[test] +fn selector_sibling_with_type_modifier() { + // selector "sibling" + modifiers ["type"] matches only type sibling imports + assert_format( + r#" +import a from "a"; +import b from "./b"; +import type c from "./c"; +import type d from "./d"; +import e from "e"; +"#, + r#" +{ + "experimentalSortImports": { + "customGroups": [ + { + "groupName": "typeSiblings", + "selector": "sibling", + "modifiers": ["type"] + } + ], + "groups": [ + "typeSiblings", + "unknown" + ] + } +} +"#, + r#" +import type c from "./c"; +import type d from "./d"; + +import b from "./b"; +import a from "a"; +import e from "e"; +"#, + ); +} diff --git a/npm/oxfmt/configuration_schema.json b/npm/oxfmt/configuration_schema.json index 1895b4605ff9c..daeb3353f9d4a 100644 --- a/npm/oxfmt/configuration_schema.json +++ b/npm/oxfmt/configuration_schema.json @@ -278,6 +278,25 @@ "default": "", "type": "string", "markdownDescription": "Name of the custom group, used in the `groups` option." + }, + "modifiers": { + "description": "Modifiers to match the import characteristics.\nAll specified modifiers must be present (AND logic).\n\nPossible values: `\"side-effect\"`, `\"type\"`, `\"value\"`, `\"default\"`, `\"wildcard\"`, `\"named\"`", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + }, + "markdownDescription": "Modifiers to match the import characteristics.\nAll specified modifiers must be present (AND logic).\n\nPossible values: `\"side-effect\"`, `\"type\"`, `\"value\"`, `\"default\"`, `\"wildcard\"`, `\"named\"`" + }, + "selector": { + "description": "Selector to match the import kind.\n\nPossible values: `\"type\"`, `\"side-effect-style\"`, `\"side-effect\"`, `\"style\"`, `\"index\"`,\n`\"sibling\"`, `\"parent\"`, `\"subpath\"`, `\"internal\"`, `\"builtin\"`, `\"external\"`, `\"import\"`", + "type": [ + "string", + "null" + ], + "markdownDescription": "Selector to match the import kind.\n\nPossible values: `\"type\"`, `\"side-effect-style\"`, `\"side-effect\"`, `\"style\"`, `\"index\"`,\n`\"sibling\"`, `\"parent\"`, `\"subpath\"`, `\"internal\"`, `\"builtin\"`, `\"external\"`, `\"import\"`" } } }, @@ -610,7 +629,7 @@ "type": "object", "properties": { "customGroups": { - "description": "Define your own groups for matching very specific imports.\n\nThe `customGroups` list is ordered: The first definition that matches an element will be used.\nCustom groups have a higher priority than any predefined group.\n\nIf you want a predefined group to take precedence over a custom group,\nyou must write a custom group definition that does the same as what the predefined group does, and put it first in the list.\n\n- Default: `[]`", + "description": "Define your own groups for matching very specific imports.\n\nThe `customGroups` list is ordered: The first definition that matches an element will be used.\nCustom groups have a higher priority than any predefined group.\n\nIf you want a predefined group to take precedence over a custom group,\nyou must write a custom group definition that does the same as what the predefined group does, and put it first in the list.\n\nIf you specify multiple conditions like `elementNamePattern`, `selector`, and `modifiers`,\nall conditions must be met for an import to match the custom group (AND logic).\n\n- Default: `[]`", "type": [ "array", "null" @@ -618,10 +637,10 @@ "items": { "$ref": "#/definitions/CustomGroupItemConfig" }, - "markdownDescription": "Define your own groups for matching very specific imports.\n\nThe `customGroups` list is ordered: The first definition that matches an element will be used.\nCustom groups have a higher priority than any predefined group.\n\nIf you want a predefined group to take precedence over a custom group,\nyou must write a custom group definition that does the same as what the predefined group does, and put it first in the list.\n\n- Default: `[]`" + "markdownDescription": "Define your own groups for matching very specific imports.\n\nThe `customGroups` list is ordered: The first definition that matches an element will be used.\nCustom groups have a higher priority than any predefined group.\n\nIf you want a predefined group to take precedence over a custom group,\nyou must write a custom group definition that does the same as what the predefined group does, and put it first in the list.\n\nIf you specify multiple conditions like `elementNamePattern`, `selector`, and `modifiers`,\nall conditions must be met for an import to match the custom group (AND logic).\n\n- Default: `[]`" }, "groups": { - "description": "Specifies a list of predefined import groups for sorting.\n\nEach import will be assigned a single group specified in the groups option (or the `unknown` group if no match is found).\nThe order of items in the `groups` option determines how groups are ordered.\n\nWithin a given group, members will be sorted according to the type, order, ignoreCase, etc. options.\n\nIndividual groups can be combined together by placing them in an array.\nThe order of groups in that array does not matter.\nAll members of the groups in the array will be sorted together as if they were part of a single group.\n\nPredefined groups are characterized by a single selector and potentially multiple modifiers.\nYou may enter modifiers in any order, but the selector must always come at the end.\n\nThe list of selectors is sorted from most to least important:\n- `type` — TypeScript type imports.\n- `side-effect-style` — Side effect style imports.\n- `side-effect` — Side effect imports.\n- `style` — Style imports.\n- `index` — Main file from the current directory.\n- `sibling` — Modules from the same directory.\n- `parent` — Modules from the parent directory.\n- `subpath` — Node.js subpath imports.\n- `internal` — Your internal modules.\n- `builtin` — Node.js Built-in Modules.\n- `external` — External modules installed in the project.\n- `import` — Any import.\n\nThe list of modifiers is sorted from most to least important:\n- `side-effect` — Side effect imports.\n- `type` — TypeScript type imports.\n- `value` — Value imports.\n- `default` — Imports containing the default specifier.\n- `wildcard` — Imports containing the wildcard (`* as`) specifier.\n- `named` — Imports containing at least one named specifier.\n- `multiline` — Imports on multiple lines.\n- `singleline` — Imports on a single line.\n\nSee also for details.\n\n- Default: See below\n```json\n[\n\"type-import\",\n[\"value-builtin\", \"value-external\"],\n\"type-internal\",\n\"value-internal\",\n[\"type-parent\", \"type-sibling\", \"type-index\"],\n[\"value-parent\", \"value-sibling\", \"value-index\"],\n\"unknown\",\n]\n```", + "description": "Specifies a list of predefined import groups for sorting.\n\nEach import will be assigned a single group specified in the groups option (or the `unknown` group if no match is found).\nThe order of items in the `groups` option determines how groups are ordered.\n\nWithin a given group, members will be sorted according to the type, order, ignoreCase, etc. options.\n\nIndividual groups can be combined together by placing them in an array.\nThe order of groups in that array does not matter.\nAll members of the groups in the array will be sorted together as if they were part of a single group.\n\nPredefined groups are characterized by a single selector and potentially multiple modifiers.\nYou may enter modifiers in any order, but the selector must always come at the end.\n\nThe list of selectors is sorted from most to least important:\n- `type` — TypeScript type imports.\n- `side-effect-style` — Side effect style imports.\n- `side-effect` — Side effect imports.\n- `style` — Style imports.\n- `index` — Main file from the current directory.\n- `sibling` — Modules from the same directory.\n- `parent` — Modules from the parent directory.\n- `subpath` — Node.js subpath imports.\n- `internal` — Your internal modules.\n- `builtin` — Node.js Built-in Modules.\n- `external` — External modules installed in the project.\n- `import` — Any import.\n\nThe list of modifiers is sorted from most to least important:\n- `side-effect` — Side effect imports.\n- `type` — TypeScript type imports.\n- `value` — Value imports.\n- `default` — Imports containing the default specifier.\n- `wildcard` — Imports containing the wildcard (`* as`) specifier.\n- `named` — Imports containing at least one named specifier.\n\n- Default: See below\n```json\n[\n\"type-import\",\n[\"value-builtin\", \"value-external\"],\n\"type-internal\",\n\"value-internal\",\n[\"type-parent\", \"type-sibling\", \"type-index\"],\n[\"value-parent\", \"value-sibling\", \"value-index\"],\n\"unknown\",\n]\n```", "type": [ "array", "null" @@ -629,7 +648,7 @@ "items": { "$ref": "#/definitions/SortGroupItemConfig" }, - "markdownDescription": "Specifies a list of predefined import groups for sorting.\n\nEach import will be assigned a single group specified in the groups option (or the `unknown` group if no match is found).\nThe order of items in the `groups` option determines how groups are ordered.\n\nWithin a given group, members will be sorted according to the type, order, ignoreCase, etc. options.\n\nIndividual groups can be combined together by placing them in an array.\nThe order of groups in that array does not matter.\nAll members of the groups in the array will be sorted together as if they were part of a single group.\n\nPredefined groups are characterized by a single selector and potentially multiple modifiers.\nYou may enter modifiers in any order, but the selector must always come at the end.\n\nThe list of selectors is sorted from most to least important:\n- `type` — TypeScript type imports.\n- `side-effect-style` — Side effect style imports.\n- `side-effect` — Side effect imports.\n- `style` — Style imports.\n- `index` — Main file from the current directory.\n- `sibling` — Modules from the same directory.\n- `parent` — Modules from the parent directory.\n- `subpath` — Node.js subpath imports.\n- `internal` — Your internal modules.\n- `builtin` — Node.js Built-in Modules.\n- `external` — External modules installed in the project.\n- `import` — Any import.\n\nThe list of modifiers is sorted from most to least important:\n- `side-effect` — Side effect imports.\n- `type` — TypeScript type imports.\n- `value` — Value imports.\n- `default` — Imports containing the default specifier.\n- `wildcard` — Imports containing the wildcard (`* as`) specifier.\n- `named` — Imports containing at least one named specifier.\n- `multiline` — Imports on multiple lines.\n- `singleline` — Imports on a single line.\n\nSee also for details.\n\n- Default: See below\n```json\n[\n\"type-import\",\n[\"value-builtin\", \"value-external\"],\n\"type-internal\",\n\"value-internal\",\n[\"type-parent\", \"type-sibling\", \"type-index\"],\n[\"value-parent\", \"value-sibling\", \"value-index\"],\n\"unknown\",\n]\n```" + "markdownDescription": "Specifies a list of predefined import groups for sorting.\n\nEach import will be assigned a single group specified in the groups option (or the `unknown` group if no match is found).\nThe order of items in the `groups` option determines how groups are ordered.\n\nWithin a given group, members will be sorted according to the type, order, ignoreCase, etc. options.\n\nIndividual groups can be combined together by placing them in an array.\nThe order of groups in that array does not matter.\nAll members of the groups in the array will be sorted together as if they were part of a single group.\n\nPredefined groups are characterized by a single selector and potentially multiple modifiers.\nYou may enter modifiers in any order, but the selector must always come at the end.\n\nThe list of selectors is sorted from most to least important:\n- `type` — TypeScript type imports.\n- `side-effect-style` — Side effect style imports.\n- `side-effect` — Side effect imports.\n- `style` — Style imports.\n- `index` — Main file from the current directory.\n- `sibling` — Modules from the same directory.\n- `parent` — Modules from the parent directory.\n- `subpath` — Node.js subpath imports.\n- `internal` — Your internal modules.\n- `builtin` — Node.js Built-in Modules.\n- `external` — External modules installed in the project.\n- `import` — Any import.\n\nThe list of modifiers is sorted from most to least important:\n- `side-effect` — Side effect imports.\n- `type` — TypeScript type imports.\n- `value` — Value imports.\n- `default` — Imports containing the default specifier.\n- `wildcard` — Imports containing the wildcard (`* as`) specifier.\n- `named` — Imports containing at least one named specifier.\n\n- Default: See below\n```json\n[\n\"type-import\",\n[\"value-builtin\", \"value-external\"],\n\"type-internal\",\n\"value-internal\",\n[\"type-parent\", \"type-sibling\", \"type-index\"],\n[\"value-parent\", \"value-sibling\", \"value-index\"],\n\"unknown\",\n]\n```" }, "ignoreCase": { "description": "Specifies whether sorting should be case-sensitive.\n\n- Default: `true`", diff --git a/tasks/website_formatter/src/snapshots/schema_json.snap b/tasks/website_formatter/src/snapshots/schema_json.snap index d1e697d38cfab..315bd093bda04 100644 --- a/tasks/website_formatter/src/snapshots/schema_json.snap +++ b/tasks/website_formatter/src/snapshots/schema_json.snap @@ -282,6 +282,25 @@ expression: json "default": "", "type": "string", "markdownDescription": "Name of the custom group, used in the `groups` option." + }, + "modifiers": { + "description": "Modifiers to match the import characteristics.\nAll specified modifiers must be present (AND logic).\n\nPossible values: `\"side-effect\"`, `\"type\"`, `\"value\"`, `\"default\"`, `\"wildcard\"`, `\"named\"`", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + }, + "markdownDescription": "Modifiers to match the import characteristics.\nAll specified modifiers must be present (AND logic).\n\nPossible values: `\"side-effect\"`, `\"type\"`, `\"value\"`, `\"default\"`, `\"wildcard\"`, `\"named\"`" + }, + "selector": { + "description": "Selector to match the import kind.\n\nPossible values: `\"type\"`, `\"side-effect-style\"`, `\"side-effect\"`, `\"style\"`, `\"index\"`,\n`\"sibling\"`, `\"parent\"`, `\"subpath\"`, `\"internal\"`, `\"builtin\"`, `\"external\"`, `\"import\"`", + "type": [ + "string", + "null" + ], + "markdownDescription": "Selector to match the import kind.\n\nPossible values: `\"type\"`, `\"side-effect-style\"`, `\"side-effect\"`, `\"style\"`, `\"index\"`,\n`\"sibling\"`, `\"parent\"`, `\"subpath\"`, `\"internal\"`, `\"builtin\"`, `\"external\"`, `\"import\"`" } } }, @@ -614,7 +633,7 @@ expression: json "type": "object", "properties": { "customGroups": { - "description": "Define your own groups for matching very specific imports.\n\nThe `customGroups` list is ordered: The first definition that matches an element will be used.\nCustom groups have a higher priority than any predefined group.\n\nIf you want a predefined group to take precedence over a custom group,\nyou must write a custom group definition that does the same as what the predefined group does, and put it first in the list.\n\n- Default: `[]`", + "description": "Define your own groups for matching very specific imports.\n\nThe `customGroups` list is ordered: The first definition that matches an element will be used.\nCustom groups have a higher priority than any predefined group.\n\nIf you want a predefined group to take precedence over a custom group,\nyou must write a custom group definition that does the same as what the predefined group does, and put it first in the list.\n\nIf you specify multiple conditions like `elementNamePattern`, `selector`, and `modifiers`,\nall conditions must be met for an import to match the custom group (AND logic).\n\n- Default: `[]`", "type": [ "array", "null" @@ -622,10 +641,10 @@ expression: json "items": { "$ref": "#/definitions/CustomGroupItemConfig" }, - "markdownDescription": "Define your own groups for matching very specific imports.\n\nThe `customGroups` list is ordered: The first definition that matches an element will be used.\nCustom groups have a higher priority than any predefined group.\n\nIf you want a predefined group to take precedence over a custom group,\nyou must write a custom group definition that does the same as what the predefined group does, and put it first in the list.\n\n- Default: `[]`" + "markdownDescription": "Define your own groups for matching very specific imports.\n\nThe `customGroups` list is ordered: The first definition that matches an element will be used.\nCustom groups have a higher priority than any predefined group.\n\nIf you want a predefined group to take precedence over a custom group,\nyou must write a custom group definition that does the same as what the predefined group does, and put it first in the list.\n\nIf you specify multiple conditions like `elementNamePattern`, `selector`, and `modifiers`,\nall conditions must be met for an import to match the custom group (AND logic).\n\n- Default: `[]`" }, "groups": { - "description": "Specifies a list of predefined import groups for sorting.\n\nEach import will be assigned a single group specified in the groups option (or the `unknown` group if no match is found).\nThe order of items in the `groups` option determines how groups are ordered.\n\nWithin a given group, members will be sorted according to the type, order, ignoreCase, etc. options.\n\nIndividual groups can be combined together by placing them in an array.\nThe order of groups in that array does not matter.\nAll members of the groups in the array will be sorted together as if they were part of a single group.\n\nPredefined groups are characterized by a single selector and potentially multiple modifiers.\nYou may enter modifiers in any order, but the selector must always come at the end.\n\nThe list of selectors is sorted from most to least important:\n- `type` — TypeScript type imports.\n- `side-effect-style` — Side effect style imports.\n- `side-effect` — Side effect imports.\n- `style` — Style imports.\n- `index` — Main file from the current directory.\n- `sibling` — Modules from the same directory.\n- `parent` — Modules from the parent directory.\n- `subpath` — Node.js subpath imports.\n- `internal` — Your internal modules.\n- `builtin` — Node.js Built-in Modules.\n- `external` — External modules installed in the project.\n- `import` — Any import.\n\nThe list of modifiers is sorted from most to least important:\n- `side-effect` — Side effect imports.\n- `type` — TypeScript type imports.\n- `value` — Value imports.\n- `default` — Imports containing the default specifier.\n- `wildcard` — Imports containing the wildcard (`* as`) specifier.\n- `named` — Imports containing at least one named specifier.\n- `multiline` — Imports on multiple lines.\n- `singleline` — Imports on a single line.\n\nSee also for details.\n\n- Default: See below\n```json\n[\n\"type-import\",\n[\"value-builtin\", \"value-external\"],\n\"type-internal\",\n\"value-internal\",\n[\"type-parent\", \"type-sibling\", \"type-index\"],\n[\"value-parent\", \"value-sibling\", \"value-index\"],\n\"unknown\",\n]\n```", + "description": "Specifies a list of predefined import groups for sorting.\n\nEach import will be assigned a single group specified in the groups option (or the `unknown` group if no match is found).\nThe order of items in the `groups` option determines how groups are ordered.\n\nWithin a given group, members will be sorted according to the type, order, ignoreCase, etc. options.\n\nIndividual groups can be combined together by placing them in an array.\nThe order of groups in that array does not matter.\nAll members of the groups in the array will be sorted together as if they were part of a single group.\n\nPredefined groups are characterized by a single selector and potentially multiple modifiers.\nYou may enter modifiers in any order, but the selector must always come at the end.\n\nThe list of selectors is sorted from most to least important:\n- `type` — TypeScript type imports.\n- `side-effect-style` — Side effect style imports.\n- `side-effect` — Side effect imports.\n- `style` — Style imports.\n- `index` — Main file from the current directory.\n- `sibling` — Modules from the same directory.\n- `parent` — Modules from the parent directory.\n- `subpath` — Node.js subpath imports.\n- `internal` — Your internal modules.\n- `builtin` — Node.js Built-in Modules.\n- `external` — External modules installed in the project.\n- `import` — Any import.\n\nThe list of modifiers is sorted from most to least important:\n- `side-effect` — Side effect imports.\n- `type` — TypeScript type imports.\n- `value` — Value imports.\n- `default` — Imports containing the default specifier.\n- `wildcard` — Imports containing the wildcard (`* as`) specifier.\n- `named` — Imports containing at least one named specifier.\n\n- Default: See below\n```json\n[\n\"type-import\",\n[\"value-builtin\", \"value-external\"],\n\"type-internal\",\n\"value-internal\",\n[\"type-parent\", \"type-sibling\", \"type-index\"],\n[\"value-parent\", \"value-sibling\", \"value-index\"],\n\"unknown\",\n]\n```", "type": [ "array", "null" @@ -633,7 +652,7 @@ expression: json "items": { "$ref": "#/definitions/SortGroupItemConfig" }, - "markdownDescription": "Specifies a list of predefined import groups for sorting.\n\nEach import will be assigned a single group specified in the groups option (or the `unknown` group if no match is found).\nThe order of items in the `groups` option determines how groups are ordered.\n\nWithin a given group, members will be sorted according to the type, order, ignoreCase, etc. options.\n\nIndividual groups can be combined together by placing them in an array.\nThe order of groups in that array does not matter.\nAll members of the groups in the array will be sorted together as if they were part of a single group.\n\nPredefined groups are characterized by a single selector and potentially multiple modifiers.\nYou may enter modifiers in any order, but the selector must always come at the end.\n\nThe list of selectors is sorted from most to least important:\n- `type` — TypeScript type imports.\n- `side-effect-style` — Side effect style imports.\n- `side-effect` — Side effect imports.\n- `style` — Style imports.\n- `index` — Main file from the current directory.\n- `sibling` — Modules from the same directory.\n- `parent` — Modules from the parent directory.\n- `subpath` — Node.js subpath imports.\n- `internal` — Your internal modules.\n- `builtin` — Node.js Built-in Modules.\n- `external` — External modules installed in the project.\n- `import` — Any import.\n\nThe list of modifiers is sorted from most to least important:\n- `side-effect` — Side effect imports.\n- `type` — TypeScript type imports.\n- `value` — Value imports.\n- `default` — Imports containing the default specifier.\n- `wildcard` — Imports containing the wildcard (`* as`) specifier.\n- `named` — Imports containing at least one named specifier.\n- `multiline` — Imports on multiple lines.\n- `singleline` — Imports on a single line.\n\nSee also for details.\n\n- Default: See below\n```json\n[\n\"type-import\",\n[\"value-builtin\", \"value-external\"],\n\"type-internal\",\n\"value-internal\",\n[\"type-parent\", \"type-sibling\", \"type-index\"],\n[\"value-parent\", \"value-sibling\", \"value-index\"],\n\"unknown\",\n]\n```" + "markdownDescription": "Specifies a list of predefined import groups for sorting.\n\nEach import will be assigned a single group specified in the groups option (or the `unknown` group if no match is found).\nThe order of items in the `groups` option determines how groups are ordered.\n\nWithin a given group, members will be sorted according to the type, order, ignoreCase, etc. options.\n\nIndividual groups can be combined together by placing them in an array.\nThe order of groups in that array does not matter.\nAll members of the groups in the array will be sorted together as if they were part of a single group.\n\nPredefined groups are characterized by a single selector and potentially multiple modifiers.\nYou may enter modifiers in any order, but the selector must always come at the end.\n\nThe list of selectors is sorted from most to least important:\n- `type` — TypeScript type imports.\n- `side-effect-style` — Side effect style imports.\n- `side-effect` — Side effect imports.\n- `style` — Style imports.\n- `index` — Main file from the current directory.\n- `sibling` — Modules from the same directory.\n- `parent` — Modules from the parent directory.\n- `subpath` — Node.js subpath imports.\n- `internal` — Your internal modules.\n- `builtin` — Node.js Built-in Modules.\n- `external` — External modules installed in the project.\n- `import` — Any import.\n\nThe list of modifiers is sorted from most to least important:\n- `side-effect` — Side effect imports.\n- `type` — TypeScript type imports.\n- `value` — Value imports.\n- `default` — Imports containing the default specifier.\n- `wildcard` — Imports containing the wildcard (`* as`) specifier.\n- `named` — Imports containing at least one named specifier.\n\n- Default: See below\n```json\n[\n\"type-import\",\n[\"value-builtin\", \"value-external\"],\n\"type-internal\",\n\"value-internal\",\n[\"type-parent\", \"type-sibling\", \"type-index\"],\n[\"value-parent\", \"value-sibling\", \"value-index\"],\n\"unknown\",\n]\n```" }, "ignoreCase": { "description": "Specifies whether sorting should be case-sensitive.\n\n- Default: `true`", diff --git a/tasks/website_formatter/src/snapshots/schema_markdown.snap b/tasks/website_formatter/src/snapshots/schema_markdown.snap index 93d81efcc3737..0d359cbefe4f9 100644 --- a/tasks/website_formatter/src/snapshots/schema_markdown.snap +++ b/tasks/website_formatter/src/snapshots/schema_markdown.snap @@ -94,6 +94,9 @@ Custom groups have a higher priority than any predefined group. If you want a predefined group to take precedence over a custom group, you must write a custom group definition that does the same as what the predefined group does, and put it first in the list. +If you specify multiple conditions like `elementNamePattern`, `selector`, and `modifiers`, +all conditions must be met for an import to match the custom group (AND logic). + - Default: `[]` @@ -123,6 +126,28 @@ default: `""` Name of the custom group, used in the `groups` option. +##### experimentalSortImports.customGroups[n].modifiers + +type: `string[]` + + +Modifiers to match the import characteristics. +All specified modifiers must be present (AND logic). + +Possible values: `"side-effect"`, `"type"`, `"value"`, `"default"`, `"wildcard"`, `"named"` + + +##### experimentalSortImports.customGroups[n].selector + +type: `string` + + +Selector to match the import kind. + +Possible values: `"type"`, `"side-effect-style"`, `"side-effect"`, `"style"`, `"index"`, +`"sibling"`, `"parent"`, `"subpath"`, `"internal"`, `"builtin"`, `"external"`, `"import"` + + ### experimentalSortImports.groups type: `array` @@ -163,10 +188,6 @@ The list of modifiers is sorted from most to least important: - `default` — Imports containing the default specifier. - `wildcard` — Imports containing the wildcard (`* as`) specifier. - `named` — Imports containing at least one named specifier. -- `multiline` — Imports on multiple lines. -- `singleline` — Imports on a single line. - -See also for details. - Default: See below ```json @@ -582,6 +603,9 @@ Custom groups have a higher priority than any predefined group. If you want a predefined group to take precedence over a custom group, you must write a custom group definition that does the same as what the predefined group does, and put it first in the list. +If you specify multiple conditions like `elementNamePattern`, `selector`, and `modifiers`, +all conditions must be met for an import to match the custom group (AND logic). + - Default: `[]` @@ -611,6 +635,28 @@ default: `""` Name of the custom group, used in the `groups` option. +######## overrides[n].options.experimentalSortImports.customGroups[n].modifiers + +type: `string[]` + + +Modifiers to match the import characteristics. +All specified modifiers must be present (AND logic). + +Possible values: `"side-effect"`, `"type"`, `"value"`, `"default"`, `"wildcard"`, `"named"` + + +######## overrides[n].options.experimentalSortImports.customGroups[n].selector + +type: `string` + + +Selector to match the import kind. + +Possible values: `"type"`, `"side-effect-style"`, `"side-effect"`, `"style"`, `"index"`, +`"sibling"`, `"parent"`, `"subpath"`, `"internal"`, `"builtin"`, `"external"`, `"import"` + + ###### overrides[n].options.experimentalSortImports.groups type: `array` @@ -651,10 +697,6 @@ The list of modifiers is sorted from most to least important: - `default` — Imports containing the default specifier. - `wildcard` — Imports containing the wildcard (`* as`) specifier. - `named` — Imports containing at least one named specifier. -- `multiline` — Imports on multiple lines. -- `singleline` — Imports on a single line. - -See also for details. - Default: See below ```json