prettier-plugin-sort 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -136,6 +136,8 @@ Set `esmImportSeparation` to `false` to remove blank lines between groups and ab
136
136
 
137
137
  `separate` is the default. Type-only default and namespace imports keep their original form.
138
138
 
139
+ `import type { T }` and `import { type T }` have different runtime behavior under [`verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig/verbatimModuleSyntax.html). The plugin converts between them only when the same module also has a value import. Otherwise, it preserves the original form to avoid changing module loading behavior.
140
+
139
141
  ### Merging and sorting boundaries
140
142
 
141
143
  With `esmImportMerge` enabled, imports from the same module are merged when it is safe to do so.
package/README.zh.md CHANGED
@@ -136,6 +136,8 @@ import App from './App';
136
136
 
137
137
  默认值为 `separate`。包含默认导入或命名空间导入的仅类型 `import` 会保留原有形式。
138
138
 
139
+ `import type { T }` 和 `import { type T }` 在 [`verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig/verbatimModuleSyntax.html) 下具有不同的运行时行为。仅当同一模块同时存在值导入时,插件才会在两种写法之间转换。否则保留原写法,避免改变模块的加载行为。
140
+
139
141
  ### 合并与排序边界
140
142
 
141
143
  启用 `esmImportMerge` 后,来自同一模块的 `import` 会在安全的情况下合并。
package/dist/index.js CHANGED
@@ -77,7 +77,7 @@ var options = {
77
77
  type: "choice",
78
78
  default: DEFAULT_SORT_OPTIONS.esmImportTypeStyle,
79
79
  category: "ES Module Imports",
80
- description: "Format named type imports as separate declarations or inline specifiers.",
80
+ description: "Format named type imports as separate declarations or inline specifiers without changing runtime module requests.",
81
81
  choices: [
82
82
  {
83
83
  value: "separate",
@@ -119,6 +119,7 @@ var options = {
119
119
 
120
120
  // src/sort-package.ts
121
121
  import findMinimumSemanticVersion from "semver/ranges/min-version.js";
122
+ import getValidSemanticVersionRange from "semver/ranges/valid.js";
122
123
 
123
124
  // src/parser-ast.ts
124
125
  function getAstNodeTextRange(node) {
@@ -911,11 +912,38 @@ function getDependencyName(dependencyIdentifier) {
911
912
  const versionSeparatorIndex = dependencyIdentifier.indexOf("@", dependencyIdentifier.startsWith("@") ? 1 : 0);
912
913
  return versionSeparatorIndex < 0 ? dependencyIdentifier : dependencyIdentifier.slice(0, versionSeparatorIndex);
913
914
  }
915
+ function getPackageVersionSeparatorIndex(packageSelector) {
916
+ return packageSelector.indexOf("@", packageSelector.startsWith("@") ? 1 : 0);
917
+ }
918
+ function isCompletePackageSelector(packageSelector) {
919
+ const versionSeparatorIndex = getPackageVersionSeparatorIndex(packageSelector);
920
+ if (versionSeparatorIndex < 0) {
921
+ return packageSelector !== "";
922
+ }
923
+ const name = packageSelector.slice(0, versionSeparatorIndex);
924
+ const versionRange = packageSelector.slice(versionSeparatorIndex + 1);
925
+ return name !== "" && versionRange.trim() !== "" && !versionRange.trimEnd().endsWith("||") && getValidSemanticVersionRange(versionRange) !== null;
926
+ }
927
+ function findPnpmDependencySeparator(packageSelector) {
928
+ for (let separatorIndex = packageSelector.indexOf(">");separatorIndex >= 0; separatorIndex = packageSelector.indexOf(">", separatorIndex + 1)) {
929
+ const isSemverComparator = packageSelector[separatorIndex - 1]?.trim() === "";
930
+ if (isSemverComparator) {
931
+ continue;
932
+ }
933
+ const parentSelector = packageSelector.slice(0, separatorIndex);
934
+ const dependencySelector = packageSelector.slice(separatorIndex + 1);
935
+ if (dependencySelector.trim() !== "" && isCompletePackageSelector(parentSelector)) {
936
+ return separatorIndex;
937
+ }
938
+ }
939
+ return -1;
940
+ }
914
941
  function parsePackageSelector(packageSelector) {
915
- const [nameAndVersion = packageSelector] = packageSelector.split(">");
916
- const versionSeparatorIndex = nameAndVersion.lastIndexOf("@");
917
- if (versionSeparatorIndex <= 0) {
918
- return { name: packageSelector, versionRange: null };
942
+ const dependencySeparatorIndex = findPnpmDependencySeparator(packageSelector);
943
+ const nameAndVersion = dependencySeparatorIndex < 0 ? packageSelector : packageSelector.slice(0, dependencySeparatorIndex);
944
+ const versionSeparatorIndex = getPackageVersionSeparatorIndex(nameAndVersion);
945
+ if (versionSeparatorIndex < 0) {
946
+ return { name: nameAndVersion, versionRange: null };
919
947
  }
920
948
  return {
921
949
  name: nameAndVersion.slice(0, versionSeparatorIndex),
@@ -1532,19 +1560,6 @@ function getLocalBindingNames(importDeclaration) {
1532
1560
  }
1533
1561
  return localBindingNames;
1534
1562
  }
1535
- function normalizeNamedTypeOnlyImport(importDeclaration) {
1536
- if (!importDeclaration.isTypeOnly || !importDeclaration.namedSpecifiers || importDeclaration.defaultBinding || importDeclaration.namespaceBinding || importDeclaration.verbatimDeclaration !== null) {
1537
- return importDeclaration;
1538
- }
1539
- return {
1540
- ...importDeclaration,
1541
- isTypeOnly: false,
1542
- namedSpecifiers: importDeclaration.namedSpecifiers.map((importSpecifier) => ({
1543
- ...importSpecifier,
1544
- isTypeOnly: true
1545
- }))
1546
- };
1547
- }
1548
1563
  function getImportRequestKey(importDeclaration) {
1549
1564
  return `${importDeclaration.moduleSpecifier}\x00${importDeclaration.importAttributes ?? ""}`;
1550
1565
  }
@@ -1564,9 +1579,10 @@ function isImportDeclarationMergeSafe(targetImportDeclaration, candidateImportDe
1564
1579
  }
1565
1580
  function mergeImportDeclarations(targetImportDeclaration, candidateImportDeclaration) {
1566
1581
  let namedSpecifiers = null;
1582
+ const isTypeOnly = targetImportDeclaration.isTypeOnly && candidateImportDeclaration.isTypeOnly;
1567
1583
  const mergedNamedSpecifiers = [
1568
- ...targetImportDeclaration.namedSpecifiers ?? [],
1569
- ...candidateImportDeclaration.namedSpecifiers ?? []
1584
+ ...getNamedSpecifiersForMerge(targetImportDeclaration, isTypeOnly),
1585
+ ...getNamedSpecifiersForMerge(candidateImportDeclaration, isTypeOnly)
1570
1586
  ];
1571
1587
  if (mergedNamedSpecifiers.length > 0) {
1572
1588
  namedSpecifiers = mergedNamedSpecifiers;
@@ -1574,7 +1590,7 @@ function mergeImportDeclarations(targetImportDeclaration, candidateImportDeclara
1574
1590
  return {
1575
1591
  moduleSpecifier: targetImportDeclaration.moduleSpecifier,
1576
1592
  moduleSpecifierText: targetImportDeclaration.moduleSpecifierText,
1577
- isTypeOnly: false,
1593
+ isTypeOnly,
1578
1594
  isSideEffectOnly: false,
1579
1595
  defaultBinding: targetImportDeclaration.defaultBinding ?? candidateImportDeclaration.defaultBinding,
1580
1596
  namespaceBinding: targetImportDeclaration.namespaceBinding ?? candidateImportDeclaration.namespaceBinding,
@@ -1586,6 +1602,12 @@ function mergeImportDeclarations(targetImportDeclaration, candidateImportDeclara
1586
1602
  isMergeBoundary: false
1587
1603
  };
1588
1604
  }
1605
+ function getNamedSpecifiersForMerge(importDeclaration, isMergedTypeOnly) {
1606
+ return (importDeclaration.namedSpecifiers ?? []).map((importSpecifier) => ({
1607
+ ...importSpecifier,
1608
+ isTypeOnly: !isMergedTypeOnly && (importDeclaration.isTypeOnly || importSpecifier.isTypeOnly)
1609
+ }));
1610
+ }
1589
1611
  function mergeCompatibleImports(importDeclarations) {
1590
1612
  const bindingCountsByImportDeclaration = new Map;
1591
1613
  const currentBindingCountsByRequest = new Map;
@@ -1613,10 +1635,9 @@ function mergeCompatibleImports(importDeclarations) {
1613
1635
  bindingCountsByImportDeclaration.set(importDeclaration, bindingCounts);
1614
1636
  }
1615
1637
  const mergedImportDeclarations = [];
1616
- for (const originalImportDeclaration of importDeclarations) {
1617
- const importDeclaration = normalizeNamedTypeOnlyImport(originalImportDeclaration);
1638
+ for (const importDeclaration of importDeclarations) {
1618
1639
  const importRequestKey = getImportRequestKey(importDeclaration);
1619
- const bindingCounts = bindingCountsByImportDeclaration.get(originalImportDeclaration) ?? {
1640
+ const bindingCounts = bindingCountsByImportDeclaration.get(importDeclaration) ?? {
1620
1641
  defaultBindingCount: 0,
1621
1642
  namespaceBindingCount: 0
1622
1643
  };
@@ -1638,18 +1659,19 @@ function applyTypeImportStyle(importDeclaration, typeImportStyle) {
1638
1659
  return [importDeclaration];
1639
1660
  }
1640
1661
  const namedSpecifiers = importDeclaration.namedSpecifiers;
1662
+ if (importDeclaration.isTypeOnly) {
1663
+ return [
1664
+ {
1665
+ ...importDeclaration,
1666
+ namedSpecifiers: sortNamedImportSpecifiers(namedSpecifiers)
1667
+ }
1668
+ ];
1669
+ }
1641
1670
  if (typeImportStyle === "separate") {
1642
- if (importDeclaration.isTypeOnly) {
1643
- return [
1644
- {
1645
- ...importDeclaration,
1646
- namedSpecifiers: sortNamedImportSpecifiers(namedSpecifiers)
1647
- }
1648
- ];
1649
- }
1650
1671
  const typeSpecifiers2 = namedSpecifiers.filter((importSpecifier) => importSpecifier.isTypeOnly);
1651
1672
  const valueSpecifiers2 = namedSpecifiers.filter((importSpecifier) => !importSpecifier.isTypeOnly);
1652
- if (typeSpecifiers2.length > 0 && (importDeclaration.importAttributes !== null || importDeclaration.leadingCommentsText.trim() !== "" || importDeclaration.trailingCommentsText !== "")) {
1673
+ const hasValueBinding = valueSpecifiers2.length > 0 || importDeclaration.defaultBinding !== null || importDeclaration.namespaceBinding !== null;
1674
+ if (typeSpecifiers2.length > 0 && (!hasValueBinding || importDeclaration.importAttributes !== null || importDeclaration.leadingCommentsText.trim() !== "" || importDeclaration.trailingCommentsText !== "")) {
1653
1675
  return [
1654
1676
  {
1655
1677
  ...importDeclaration,
@@ -1687,32 +1709,21 @@ function applyTypeImportStyle(importDeclaration, typeImportStyle) {
1687
1709
  }
1688
1710
  return styledImportDeclarations;
1689
1711
  }
1690
- let inlineImportDeclaration = { ...importDeclaration, namedSpecifiers };
1691
- if (importDeclaration.isTypeOnly) {
1692
- inlineImportDeclaration = {
1693
- ...importDeclaration,
1694
- isTypeOnly: false,
1695
- namedSpecifiers: namedSpecifiers.map((importSpecifier) => ({
1696
- ...importSpecifier,
1697
- isTypeOnly: true
1698
- }))
1699
- };
1700
- }
1701
1712
  if (typeImportStyle === "mixed") {
1702
1713
  return [
1703
1714
  {
1704
- ...inlineImportDeclaration,
1705
- namedSpecifiers: sortNamedImportSpecifiers(inlineImportDeclaration.namedSpecifiers)
1715
+ ...importDeclaration,
1716
+ namedSpecifiers: sortNamedImportSpecifiers(namedSpecifiers)
1706
1717
  }
1707
1718
  ];
1708
1719
  }
1709
- const typeSpecifiers = sortNamedImportSpecifiers(inlineImportDeclaration.namedSpecifiers.filter((importSpecifier) => importSpecifier.isTypeOnly));
1710
- const valueSpecifiers = sortNamedImportSpecifiers(inlineImportDeclaration.namedSpecifiers.filter((importSpecifier) => !importSpecifier.isTypeOnly));
1720
+ const typeSpecifiers = sortNamedImportSpecifiers(namedSpecifiers.filter((importSpecifier) => importSpecifier.isTypeOnly));
1721
+ const valueSpecifiers = sortNamedImportSpecifiers(namedSpecifiers.filter((importSpecifier) => !importSpecifier.isTypeOnly));
1711
1722
  let orderedSpecifiers = [...valueSpecifiers, ...typeSpecifiers];
1712
1723
  if (typeImportStyle === "inline-first") {
1713
1724
  orderedSpecifiers = [...typeSpecifiers, ...valueSpecifiers];
1714
1725
  }
1715
- return [{ ...inlineImportDeclaration, namedSpecifiers: orderedSpecifiers }];
1726
+ return [{ ...importDeclaration, namedSpecifiers: orderedSpecifiers }];
1716
1727
  }
1717
1728
  function getImportBindingShapeRank(importDeclaration) {
1718
1729
  if (importDeclaration.defaultBinding !== null) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prettier-plugin-sort",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "A Prettier plugin for sorting ES module imports, export specifiers, and package.json keys.",
5
5
  "keywords": [
6
6
  "imports",
@@ -29,18 +29,17 @@
29
29
  "default": "./dist/index.js"
30
30
  }
31
31
  },
32
- "main": "./dist/index.js",
33
- "types": "./dist/index.d.ts",
34
32
  "files": [
35
33
  "dist"
36
34
  ],
37
35
  "scripts": {
38
36
  "build": "bun run scripts/build.ts",
39
- "format": "prettier **/*.{json,ts,md} --write --experimental-cli",
40
- "lint": "eslint src/ --cache",
41
- "lint:fix": "eslint src/ --fix --cache",
37
+ "format": "prettier \"**/*.{json,md,ts,yaml,yml}\" --write --experimental-cli",
38
+ "format:check": "prettier \"**/*.{json,md,ts,yaml,yml}\" --check --experimental-cli",
39
+ "lint": "eslint --cache",
40
+ "lint:fix": "eslint --fix --cache",
42
41
  "prepare": "husky",
43
- "test": "bun test tests/",
42
+ "test": "bun test",
44
43
  "typecheck": "tsc --noEmit"
45
44
  },
46
45
  "dependencies": {