my-code-style 1.4.0 → 1.6.0

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.
@@ -25,34 +25,88 @@ const PLURAL_MAP = {
25
25
  assets: "asset",
26
26
  }
27
27
 
28
+ // Plurals whose singular cannot be derived by rule: uncountables, irregulars,
29
+ // and the "-e" + "s" words that would otherwise lose their stem ("caches" →
30
+ // "cach"). Everything else is handled by the suffix rules below.
31
+ const EXACT_SINGULAR = {
32
+ news: "news",
33
+ series: "series",
34
+ movies: "movie",
35
+ cookies: "cookie",
36
+ caches: "cache",
37
+ chases: "chase",
38
+ niches: "niche",
39
+ }
40
+
41
+ // Directory names that already read as singular (or are conventional as-is)
42
+ const NON_PLURAL = [
43
+ "status",
44
+ "canvas",
45
+ "bus",
46
+ "pass",
47
+ "process",
48
+ "css",
49
+ "less",
50
+ "scss",
51
+ "js",
52
+ "ts",
53
+ "types",
54
+ ]
55
+
56
+ // Singulars that legitimately end in "s"/"z" and therefore take "-es":
57
+ // bus → buses, status → statuses. Without this list "buses" would follow the
58
+ // "-se + s" reading and become "buse".
59
+ const SIBILANT_SINGULARS = [
60
+ "bus",
61
+ "gas",
62
+ "canvas",
63
+ "alias",
64
+ "atlas",
65
+ "bias",
66
+ "virus",
67
+ "status",
68
+ "process",
69
+ "lens",
70
+ "plus",
71
+ "focus",
72
+ "campus",
73
+ "analysis",
74
+ "axis",
75
+ "basis",
76
+ "crisis",
77
+ "thesis",
78
+ "quiz",
79
+ ]
80
+
81
+ // Unambiguous "-es" plurals: the stem ends in a doubled sibilant or a digraph
82
+ // (classes, boxes, churches, dishes, buzzes, batches, fixes).
83
+ const ES_PLURAL = /(?:ss|zz|ch|sh|x)es$/i
84
+ // "-ses"/"-zes" are ambiguous: "cases" is case + s, "buses" is bus + es.
85
+ const S_OR_Z_ES_PLURAL = /[sz]es$/i
86
+
28
87
  function toSingular(name) {
29
88
  if (!name) {
30
89
  return name
31
90
  }
32
- if (PLURAL_MAP[name]) {
33
- return PLURAL_MAP[name]
91
+ const lower = name.toLowerCase()
92
+ if (PLURAL_MAP[lower]) {
93
+ return PLURAL_MAP[lower]
94
+ }
95
+ if (EXACT_SINGULAR[lower]) {
96
+ return EXACT_SINGULAR[lower]
34
97
  }
35
- if (name.endsWith("ies") && name.length > 4) {
98
+ if (NON_PLURAL.includes(lower)) {
99
+ return name
100
+ }
101
+ if (/ies$/i.test(name) && name.length > 4) {
36
102
  return name.slice(0, -3) + "y"
37
103
  }
38
- if (/(?:[sxz]|[ch|sh])es$/.test(name)) {
104
+ if (ES_PLURAL.test(name)) {
39
105
  return name.slice(0, -2)
40
106
  }
41
- const nonPluralEndingInS = [
42
- "status",
43
- "canvas",
44
- "bus",
45
- "pass",
46
- "process",
47
- "css",
48
- "less",
49
- "scss",
50
- "js",
51
- "ts",
52
- "types",
53
- ]
54
- if (nonPluralEndingInS.includes(name.toLowerCase())) {
55
- return name
107
+ if (S_OR_Z_ES_PLURAL.test(name)) {
108
+ const esStem = name.slice(0, -2).toLowerCase()
109
+ return SIBILANT_SINGULARS.includes(esStem) ? name.slice(0, -2) : name.slice(0, -1)
56
110
  }
57
111
  if (name.endsWith("s") && !name.endsWith("ss") && name.length > 3) {
58
112
  return name.slice(0, -1)
@@ -71,11 +125,15 @@ function generateScopes(srcDirs = "src") {
71
125
 
72
126
  for (const srcDir of dirs) {
73
127
  const resolved = path.resolve(process.cwd(), srcDir)
74
- if (!fs.existsSync(resolved)) {
128
+ let entries
129
+ try {
130
+ // A missing path, a plain file, or an unreadable directory must not
131
+ // break the commitlint config load — just yield no scopes.
132
+ entries = fs.readdirSync(resolved, { withFileTypes: true })
133
+ } catch {
75
134
  continue
76
135
  }
77
- const scopes = fs
78
- .readdirSync(resolved, { withFileTypes: true })
136
+ const scopes = entries
79
137
  .filter((dirent) => dirent.isDirectory())
80
138
  .map((dirent) => toSingular(dirent.name))
81
139
  allScopes.push(...scopes)
@@ -89,6 +147,8 @@ function getStagedFiles(cwd) {
89
147
  const output = execSync("git diff --cached --name-only -z", {
90
148
  cwd,
91
149
  stdio: ["pipe", "pipe", "ignore"],
150
+ // 大仓库/大量暂存文件可能超过默认 1MB,避免 ENOBUFS 导致 scope 猜测整体失败
151
+ maxBuffer: 64 * 1024 * 1024,
92
152
  }).toString()
93
153
  return output.split("\0").filter(Boolean)
94
154
  } catch {
@@ -96,6 +156,7 @@ function getStagedFiles(cwd) {
96
156
  const output = execSync("git status --porcelain -z", {
97
157
  cwd,
98
158
  stdio: ["pipe", "pipe", "ignore"],
159
+ maxBuffer: 64 * 1024 * 1024,
99
160
  }).toString()
100
161
  const parts = output.split("\0")
101
162
  const staged = []
@@ -1,4 +1,9 @@
1
1
  // Base ESLint config for TypeScript projects
2
+ //
3
+ // Prettier 选项以 src/prettier/index.cjs 为唯一来源,避免 rules 与 prettier 配置文件漂移
4
+ const prettierConfig = require("../prettier/index.cjs")
5
+ const { overrides: _prettierOverrides, ...prettierOptions } = prettierConfig
6
+
2
7
  module.exports = {
3
8
  env: {
4
9
  browser: true,
@@ -20,6 +25,17 @@ module.exports = {
20
25
  jsx: true,
21
26
  },
22
27
  },
28
+ // Mirrors the ignores of src/eslint/flat/base.mjs: without them `eslint .`
29
+ // lints build output (dist/, coverage/) and fails on generated code.
30
+ ignorePatterns: [
31
+ "**/node_modules/**",
32
+ "**/dist/**",
33
+ "**/coverage/**",
34
+ "**/public/**",
35
+ "**/assets/iconfont/**",
36
+ "**/*.min.js",
37
+ "**/*.min.css",
38
+ ],
23
39
  plugins: ["@typescript-eslint", "prettier", "import"],
24
40
  overrides: [
25
41
  {
@@ -29,20 +45,14 @@ module.exports = {
29
45
  ],
30
46
  rules: {
31
47
  // Prettier 集成
32
- "prettier/prettier": [
33
- "error",
34
- {
35
- singleQuote: false,
36
- tabWidth: 4,
37
- semi: false,
38
- trailingComma: "all",
39
- endOfLine: "lf",
40
- },
41
- ],
48
+ "prettier/prettier": ["error", prettierOptions],
42
49
  // 格式化与风格
43
50
  indent: "off",
44
51
  semi: ["error", "never"],
45
- quotes: ["error", "double", { avoidEscape: false, allowTemplateLiterals: true }],
52
+ // avoidEscape must stay true: Prettier keeps the cheaper quote when a
53
+ // string contains double quotes ('say "hi"'), so requiring an escape
54
+ // here produces an error that --fix can never resolve.
55
+ quotes: ["error", "double", { avoidEscape: true, allowTemplateLiterals: true }],
46
56
  curly: ["error", "all"],
47
57
  "comma-dangle": ["error", "always-multiline"],
48
58
  "object-curly-spacing": ["error", "always"],
@@ -1,19 +1,21 @@
1
1
  // Shared constants and rule objects for Flat Config files
2
+ import prettierConfig from "../../prettier/index.cjs"
3
+
4
+ // Prettier 选项以 src/prettier/index.cjs 为唯一来源,避免 rules 与配置文件漂移
5
+ const { overrides: _prettierOverrides, ...prettierOptions } = prettierConfig
2
6
 
3
7
  /**
4
8
  * Prettier plugin rules — mirrors base.cjs prettier/prettier config
5
9
  */
6
10
  export const prettierRules = {
7
- "prettier/prettier": [
8
- "error",
9
- {
10
- singleQuote: false,
11
- tabWidth: 4,
12
- semi: false,
13
- trailingComma: "all",
14
- endOfLine: "lf",
15
- },
16
- ],
11
+ "prettier/prettier": ["error", prettierOptions],
12
+ }
13
+
14
+ /**
15
+ * .nvue 覆写:规则内联 options 会整体替换而非合并,必须携带完整选项
16
+ */
17
+ export const nvuePrettierRules = {
18
+ "prettier/prettier": ["error", { ...prettierOptions, parser: "vue", semi: true }],
17
19
  }
18
20
 
19
21
  /**
@@ -23,7 +25,10 @@ export const commonRules = {
23
25
  // 缩进由 Prettier 统一负责,关闭 ESLint 缩进避免打架
24
26
  indent: "off",
25
27
  semi: ["error", "never"],
26
- quotes: ["error", "double", { avoidEscape: false, allowTemplateLiterals: true }],
28
+ // avoidEscape must stay true: Prettier keeps the cheaper quote when a
29
+ // string contains double quotes ('say "hi"'), so requiring an escape here
30
+ // produces an error that --fix can never resolve.
31
+ quotes: ["error", "double", { avoidEscape: true, allowTemplateLiterals: true }],
27
32
  curly: ["error", "all"],
28
33
  "comma-dangle": ["error", "always-multiline"],
29
34
  "object-curly-spacing": ["error", "always"],
@@ -1,6 +1,7 @@
1
1
  // Base ESLint Flat Config for TypeScript projects (ESLint v9+)
2
2
  // Equivalent to src/eslint/base.cjs but in flat config format
3
3
 
4
+ import js from "@eslint/js"
4
5
  import tseslint from "typescript-eslint"
5
6
  import importX from "eslint-plugin-import-x"
6
7
  import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"
@@ -22,12 +23,17 @@ export default [
22
23
  ],
23
24
  },
24
25
 
26
+ // ESLint 官方推荐规则
27
+ // Replaces: extends: ["eslint:recommended"] — 缺失它会让 flat 配置弱于传统配置
28
+ // (no-debugger / no-cond-assign / no-constant-condition / no-empty 等不会被检查)
29
+ js.configs.recommended,
30
+
25
31
  // TypeScript recommended rules
26
- // Replaces: extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"]
32
+ // Replaces: extends: ["plugin:@typescript-eslint/recommended"]
27
33
  ...tseslint.configs.recommended,
28
34
 
29
35
  // eslint-plugin-import-x configuration
30
- // Replaces: plugin:import/recommended + eslint-config-standard (via FlatCompat)
36
+ // Replaces: plugin:import/recommended (ESLint 8 equivalent in src/eslint/base.cjs)
31
37
  {
32
38
  files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx,vue,nvue}"],
33
39
  plugins: {
@@ -66,7 +72,16 @@ export default [
66
72
  },
67
73
  },
68
74
 
69
- // Shared rules for all JS, TS, JSX, TSX and Vue files
75
+ // Prettier integration (replaces: extends: ["plugin:prettier/recommended"])
76
+ eslintPluginPrettierRecommended,
77
+
78
+ // eslint-config-prettier disables rules that conflict with Prettier —
79
+ // including `curly`, which Prettier never rewrites.
80
+ eslintConfigPrettier,
81
+
82
+ // Shared rules for all JS, TS, JSX, TSX and Vue files.
83
+ // MUST come after eslint-config-prettier: in flat config a later entry wins,
84
+ // so explicit rules such as `curly` would otherwise be silently switched off.
70
85
  {
71
86
  files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx,vue,nvue}"],
72
87
  rules: {
@@ -83,19 +98,14 @@ export default [
83
98
  },
84
99
  },
85
100
 
86
- // .nvue file handling (uni-app)
101
+ // .nvue file handling (uni-app) — mirrors the legacy config, which parses
102
+ // nvue script blocks as modules so `import` works.
87
103
  {
88
104
  files: ["**/*.nvue"],
89
105
  languageOptions: {
90
106
  parserOptions: {
91
- sourceType: "script",
107
+ sourceType: "module",
92
108
  },
93
109
  },
94
110
  },
95
-
96
- // Prettier integration (replaces: extends: ["plugin:prettier/recommended"])
97
- eslintPluginPrettierRecommended,
98
-
99
- // eslint-config-prettier MUST be last — disables conflicting ESLint rules
100
- eslintConfigPrettier,
101
111
  ]
@@ -4,6 +4,7 @@ import pluginVue from "eslint-plugin-vue"
4
4
  import tseslint from "typescript-eslint"
5
5
  import parserVue from "vue-eslint-parser"
6
6
  import baseConfig from "./base.mjs"
7
+ import { nvuePrettierRules } from "./_shared.mjs"
7
8
 
8
9
  export default [
9
10
  // Spread all base configs first
@@ -49,8 +50,12 @@ export default [
49
50
  },
50
51
  rules: {
51
52
  "vue/comment-directive": "off",
53
+ // .nvue uses semicolons (see src/prettier/index.cjs), so the base
54
+ // `semi: ["error", "never"]` rule must be disabled here — mirrors
55
+ // the legacy config in src/eslint/vue3.cjs.
56
+ semi: "off",
52
57
  // Prettier cannot infer a parser from the custom .nvue extension.
53
- "prettier/prettier": ["error", { parser: "vue", semi: true }],
58
+ ...nvuePrettierRules,
54
59
  },
55
60
  },
56
61
  ]
@@ -1,6 +1,10 @@
1
1
  // Vue 3 ESLint config — extends base, adds Vue-specific rules
2
2
  const base = require("./base.cjs")
3
3
 
4
+ // .nvue 覆写必须携带完整 Prettier 选项:规则内联 options 会整体替换而非合并
5
+ const prettierConfig = require("../prettier/index.cjs")
6
+ const { overrides: _prettierOverrides, ...prettierOptions } = prettierConfig
7
+
4
8
  module.exports = {
5
9
  ...base,
6
10
  parser: "vue-eslint-parser",
@@ -25,7 +29,7 @@ module.exports = {
25
29
  rules: {
26
30
  "vue/comment-directive": "off",
27
31
  semi: "off",
28
- "prettier/prettier": ["error", { parser: "vue", semi: true }],
32
+ "prettier/prettier": ["error", { ...prettierOptions, parser: "vue", semi: true }],
29
33
  },
30
34
  },
31
35
  ],
package/src/gitignore CHANGED
@@ -1,5 +1,6 @@
1
1
  node_modules/
2
2
  dist/
3
+ unpackage/
3
4
  coverage/
4
5
  *.local
5
6
  *.log
@@ -1 +1,5 @@
1
- npx --no-install commitlint --edit "${1}"
1
+ # 同 pre-commit:直接用本地 commitlint,省掉一次 npm CLI(npx)启动。
2
+ if command -v commitlint >/dev/null 2>&1; then
3
+ exec commitlint --edit "$1"
4
+ fi
5
+ npx --no-install commitlint --edit "$1"
@@ -1 +1,6 @@
1
- npx --no-install -- lint-staged
1
+ # husky 已经把 node_modules/.bin 放进 PATH,所以优先直接执行本地 lint-staged:
2
+ # 这样每次提交少起一个 npm CLI(npx)进程;找不到时再退回 npx(例如被提升到上层目录)。
3
+ if command -v lint-staged >/dev/null 2>&1; then
4
+ exec lint-staged
5
+ fi
6
+ npx --no-install -- lint-staged