@icebreakers/eslint-config 2.0.3 → 2.1.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
@@ -10,6 +10,7 @@
10
10
 
11
11
  - Node.js 18 or newer
12
12
  - ESLint 9 with flat config support
13
+ - React and Next related plugins are optional. If they are missing, `react`, `nextjs`, `query`, and React-side `a11y` helpers are skipped automatically instead of failing config resolution.
13
14
  - Install optional peer plugins when you turn on Tailwind (`eslint-plugin-tailwindcss` or `eslint-plugin-better-tailwindcss`), MDX (`eslint-plugin-mdx`), or UnoCSS (`@unocss/eslint-plugin`)
14
15
 
15
16
  ## Installation
@@ -56,21 +57,71 @@ export default icebreaker({
56
57
  a11y: true,
57
58
  nestjs: true,
58
59
  ionic: true,
59
- weapp: true,
60
+ miniProgram: true,
60
61
  formatters: true,
61
62
  })
62
63
  ```
63
64
 
64
- - `vue` – enables Vue + optionally version specific overrides (Vue 2/3) and ionic/weapp adjustments.
65
- - `react` – defers to the upstream React preset and unlocks accessibility helpers when `a11y` is enabled.
66
- - `query` – toggles the TanStack Query plugin (`@tanstack/eslint-plugin-query`) and its recommended lint rules.
65
+ - `miniProgram` – injects Mini Program globals, ignores common outputs/config files, and enables Vue-side Mini Program compatibility tweaks when `vue` is on.
66
+ - `vue` – enables Vue + optionally version specific overrides (Vue 2/3) and ionic/miniProgram adjustments.
67
+ - `react` – defers to the upstream React preset and unlocks accessibility helpers when `a11y` is enabled. If the React plugin set is not installed, this flag is ignored automatically.
68
+ - `query` – toggles the TanStack Query plugin (`@tanstack/eslint-plugin-query`) and its recommended lint rules. Missing plugin installs are treated as a no-op.
67
69
  - `tailwindcss` – pass `true` to use the built-in Tailwind flat config or provide `{ entryPoint, tailwindConfig }` for Tailwind v4/v3 projects.
68
70
  - `mdx` – activates MDX linting via `eslint-plugin-mdx`.
69
- - `a11y` – wires in JSX (React) and Vue accessibility plugins.
71
+ - `a11y` – wires in JSX (React) and Vue accessibility plugins. Missing framework-specific plugins are skipped independently.
70
72
  - `typescript` – extends the TypeScript preset and applies stricter unused diagnostics. Pair with `nestjs` for Nest specific adjustments.
71
73
  - `nestjs` – enables NestJS-centric TypeScript tweaks (empty decorated constructors, declaration merging, DI parameter properties, etc.).
72
74
  - `formatters` – keeps the built-in formatting rules enabled by default.
73
75
  - `test` – relaxes certain Vitest/Jest style rules (`test/prefer-lowercase-title`).
76
+ - `weapp` – legacy alias for `miniProgram`; kept for backward compatibility.
77
+
78
+ ### Mini Program Preset
79
+
80
+ `miniProgram: true` is the recommended API for `weapp-vite`, `wevu`, and native
81
+ Mini Program templates. It enables the following defaults:
82
+
83
+ - injects readonly globals for `wx`, `Page`, `App`, `Component`, `getApp`, `getCurrentPages`, `requirePlugin`, and `WechatMiniprogram`
84
+ - ignores `dist/**`, `.weapp-vite/**`, `miniprogram_npm/**`, `node_modules/**`, `project.config.json`, and `project.private.config.json`
85
+ - when `vue: true` is also enabled, relaxes Vue template checks for Mini Program inline tags such as `<text>`
86
+
87
+ #### Native Mini Program Minimal Config
88
+
89
+ ```ts
90
+ import { icebreaker } from '@icebreakers/eslint-config'
91
+
92
+ export default icebreaker({
93
+ miniProgram: true,
94
+ })
95
+ ```
96
+
97
+ #### weapp-vite + wevu Minimal Config
98
+
99
+ ```ts
100
+ import { icebreaker } from '@icebreakers/eslint-config'
101
+
102
+ export default icebreaker({
103
+ miniProgram: true,
104
+ vue: true,
105
+ })
106
+ ```
107
+
108
+ #### Combining With Existing Options
109
+
110
+ ```ts
111
+ import { icebreaker } from '@icebreakers/eslint-config'
112
+
113
+ export default icebreaker({
114
+ miniProgram: true,
115
+ vue: true,
116
+ tailwindcss: true,
117
+ ignores: [
118
+ 'coverage/**',
119
+ ],
120
+ })
121
+ ```
122
+
123
+ `miniProgram` only adds the platform defaults. User supplied `ignores`,
124
+ `extends`, `rules`, and downstream flat config items still compose normally.
74
125
 
75
126
  ### Stylelint Bridge
76
127
 
@@ -147,6 +198,6 @@ You may also pass other flat configs (e.g. from in-house presets) as additional
147
198
 
148
199
  ## Troubleshooting
149
200
 
150
- - Missing plugin errors usually mean the optional dependency is not installed in the current workspace. Add it with `pnpm add -D`.
201
+ - Missing plugin errors usually mean a feature is enabled without its optional dependency being installed in the current workspace. React and Next related presets now auto-skip in that case; other features can be added with `pnpm add -D`.
151
202
  - When combining legacy `.eslintrc` projects, prefer `icebreakerLegacy()` and move overrides into flat config format incrementally.
152
203
  - Tailwind class validation reads from your `tailwind.config.*`; double check the path when using monorepo roots or custom build tooling.
package/README.zh.md CHANGED
@@ -8,6 +8,7 @@
8
8
 
9
9
  - Node.js 18 或更高版本
10
10
  - 支持 Flat Config 的 ESLint 9
11
+ - React / Next 相关插件现在是可选项。缺失时会自动跳过 `react`、`nextjs`、`query` 以及 React 侧的 `a11y` 配置,而不是在解析配置时直接报错。
11
12
  - 如需启用 Tailwind、MDX、UnoCSS 等,可安装对应的可选依赖:`eslint-plugin-tailwindcss` / `eslint-plugin-better-tailwindcss`、`eslint-plugin-mdx`、`@unocss/eslint-plugin`
12
13
 
13
14
  ## 安装
@@ -54,21 +55,71 @@ export default icebreaker({
54
55
  a11y: true,
55
56
  nestjs: true,
56
57
  ionic: true,
57
- weapp: true,
58
+ miniProgram: true,
58
59
  formatters: true,
59
60
  })
60
61
  ```
61
62
 
62
- - `vue`:启用 Vue 规则,可根据 Vue 2/3 自动切换,并在 `ionic`、`weapp` 选项开启时追加对应覆盖。
63
- - `react`:复用上游 React 预设,配合 `a11y` 注入无障碍插件。
64
- - `query`:按需启用 TanStack Query 插件(`@tanstack/eslint-plugin-query`)及其推荐规则。
63
+ - `miniProgram`:启用小程序预设,注入全局变量、忽略常见产物/配置文件,并在 `vue: true` 时补充小程序模板兼容调整。
64
+ - `vue`:启用 Vue 规则,可根据 Vue 2/3 自动切换,并在 `ionic`、`miniProgram` 选项开启时追加对应覆盖。
65
+ - `react`:复用上游 React 预设,配合 `a11y` 注入无障碍插件;如果 React 插件组未安装,会自动忽略该选项。
66
+ - `query`:按需启用 TanStack Query 插件(`@tanstack/eslint-plugin-query`)及其推荐规则;缺少插件时按 no-op 处理。
65
67
  - `tailwindcss`:传入 `true` 使用内置 Tailwind flat 配置,或通过对象指定 Tailwind v4 的入口文件 / v3 的配置文件路径。
66
68
  - `mdx`:激活 `eslint-plugin-mdx` 处理 `.mdx` 文件。
67
- - `a11y`:按需引入 JSX 与 Vue 的无障碍规则。
69
+ - `a11y`:按需引入 JSX 与 Vue 的无障碍规则,缺少某一侧插件时只跳过对应框架配置。
68
70
  - `typescript`:开启 TypeScript 预设,加强未使用诊断,可与 `nestjs` 搭配使用以获得 Nest 专属优化。
69
71
  - `nestjs`:针对 NestJS 场景做 TypeScript 调整(允许带装饰器的空构造函数、依赖注入参数属性、声明合并等)。
70
72
  - `formatters`:默认启用格式化辅助规则。
71
73
  - `test`:放宽 Vitest / Jest 常见规则,例如关闭 `test/prefer-lowercase-title`。
74
+ - `weapp`:`miniProgram` 的兼容别名,保留但不再推荐作为主入口。
75
+
76
+ ### 小程序预设
77
+
78
+ 推荐在 `weapp-vite`、`wevu`、原生小程序模板里统一使用 `miniProgram: true`。
79
+ 它会默认完成:
80
+
81
+ - 注入 `wx`、`Page`、`App`、`Component`、`getApp`、`getCurrentPages`、`requirePlugin`、`WechatMiniprogram` 全局变量
82
+ - 忽略 `dist/**`、`.weapp-vite/**`、`miniprogram_npm/**`、`node_modules/**`、`project.config.json`、`project.private.config.json`
83
+ - 当同时开启 `vue: true` 时,降低 `<text>` 等小程序内联标签的误报
84
+
85
+ #### 原生小程序最小配置
86
+
87
+ ```ts
88
+ import { icebreaker } from '@icebreakers/eslint-config'
89
+
90
+ export default icebreaker({
91
+ miniProgram: true,
92
+ })
93
+ ```
94
+
95
+ #### weapp-vite + wevu 最小配置
96
+
97
+ ```ts
98
+ import { icebreaker } from '@icebreakers/eslint-config'
99
+
100
+ export default icebreaker({
101
+ miniProgram: true,
102
+ vue: true,
103
+ })
104
+ ```
105
+
106
+ #### 与现有选项组合
107
+
108
+ ```ts
109
+ import { icebreaker } from '@icebreakers/eslint-config'
110
+
111
+ export default icebreaker({
112
+ miniProgram: true,
113
+ vue: true,
114
+ tailwindcss: true,
115
+ ignores: [
116
+ 'coverage/**',
117
+ ],
118
+ })
119
+ ```
120
+
121
+ `miniProgram` 只负责追加平台默认项;你自己的 `ignores`、`extends`、`rules`
122
+ 以及额外 flat config 仍按原有方式继续组合。
72
123
 
73
124
  ### Stylelint 桥接
74
125
 
@@ -148,6 +199,6 @@ export default icebreaker(
148
199
 
149
200
  ## 常见问题
150
201
 
151
- - 如果提示缺少插件,说明当前工作区未安装对应可选依赖,可通过 `pnpm add -D` 补齐。
202
+ - 如果提示缺少插件,通常是某个功能已开启但当前工作区未安装对应可选依赖。React / Next 相关预设会自动跳过;其他能力可通过 `pnpm add -D` 补齐。
152
203
  - 与旧版 `.eslintrc` 混用时建议先改用 `icebreakerLegacy()`,逐步迁移至 Flat Config。
153
204
  - Tailwind 校验依赖 `tailwind.config.*`,Monorepo 或自定义构建路径时请确认配置文件位置。
package/dist/index.cjs CHANGED
@@ -31,13 +31,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
31
  enumerable: true
32
32
  }) : target, mod));
33
33
  //#endregion
34
- let node_fs = require("node:fs");
35
- node_fs = __toESM(node_fs);
36
34
  let node_module = require("node:module");
37
35
  let node_path = require("node:path");
38
36
  node_path = __toESM(node_path);
39
37
  let node_process = require("node:process");
40
38
  node_process = __toESM(node_process);
39
+ let node_url = require("node:url");
40
+ let node_fs = require("node:fs");
41
+ node_fs = __toESM(node_fs);
41
42
  //#region src/antfu.ts
42
43
  var antfu_exports = /* @__PURE__ */ __exportAll({});
43
44
  __reExport(antfu_exports, require("@antfu/eslint-config"));
@@ -104,6 +105,9 @@ const nestjsTypeScriptRules = {
104
105
  types: { Function: false }
105
106
  }]
106
107
  };
108
+ function isMiniProgramEnabled(opts) {
109
+ return opts?.miniProgram === true || opts?.weapp === true;
110
+ }
107
111
  function getDefaultVueOptions(opts) {
108
112
  const overrides = {
109
113
  "vue/attribute-hyphenation": "off",
@@ -117,7 +121,7 @@ function getDefaultVueOptions(opts) {
117
121
  "vue/no-unused-refs": "warn"
118
122
  };
119
123
  if (opts?.ionic) overrides["vue/no-deprecated-slot-attribute"] = "off";
120
- if (opts?.weapp) overrides["vue/singleline-html-element-content-newline"] = ["warn", {
124
+ if (isMiniProgramEnabled(opts)) overrides["vue/singleline-html-element-content-newline"] = ["warn", {
121
125
  ignoreWhenNoAttributes: true,
122
126
  ignoreWhenEmpty: true,
123
127
  ignores: ["text", ...INLINE_ELEMENTS],
@@ -148,27 +152,56 @@ function getDefaultTypescriptOptions(opts) {
148
152
  return { overrides };
149
153
  }
150
154
  //#endregion
155
+ //#region src/utils.ts
156
+ const require$2 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
157
+ const PACKAGE_DIR = node_path.default.dirname((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
158
+ function isObject(o) {
159
+ return Object.prototype.toString.call(o) === "[object Object]";
160
+ }
161
+ function isPackageAvailable$1(name, searchPaths = [node_process.default.cwd(), PACKAGE_DIR]) {
162
+ try {
163
+ require$2.resolve(name, { paths: searchPaths });
164
+ return true;
165
+ } catch {
166
+ return false;
167
+ }
168
+ }
169
+ function hasAllPackages(names, searchPaths) {
170
+ return names.every((name) => isPackageAvailable$1(name, searchPaths));
171
+ }
172
+ //#endregion
151
173
  //#region src/features.ts
174
+ const BETTER_TAILWIND_PACKAGES = ["eslint-plugin-better-tailwindcss"];
175
+ const TAILWIND_PACKAGES = ["eslint-plugin-tailwindcss"];
176
+ const STYLELINT_BRIDGE_PACKAGES = ["eslint-plugin-better-stylelint"];
177
+ const MDX_PACKAGES = ["eslint-plugin-mdx"];
178
+ const VUE_A11Y_PACKAGES = ["eslint-plugin-vuejs-accessibility"];
179
+ const REACT_A11Y_PACKAGES = ["eslint-plugin-jsx-a11y"];
180
+ const QUERY_PACKAGES = ["@tanstack/eslint-plugin-query"];
152
181
  function resolveStylelintConfigLoader() {
153
182
  return require("url").pathToFileURL(__filename).href.endsWith(".ts") ? new URL("./stylelint.ts", require("url").pathToFileURL(__filename).href).href : new URL("./stylelint.js", require("url").pathToFileURL(__filename).href).href;
154
183
  }
155
184
  function resolveTailwindPresets(option) {
156
185
  if (!option) return [];
157
- if (typeof option === "object") return [(0, antfu_exports.interopDefault)(import("eslint-plugin-better-tailwindcss")).then((eslintPluginBetterTailwindcss) => {
158
- const betterTailwindcssRules = {
159
- ...eslintPluginBetterTailwindcss.configs["recommended-warn"].rules,
160
- ...eslintPluginBetterTailwindcss.configs["recommended-error"].rules
161
- };
162
- return {
163
- name: "icebreaker/better-tailwindcss",
164
- plugins: { "better-tailwindcss": eslintPluginBetterTailwindcss },
165
- rules: betterTailwindcssRules,
166
- settings: { "better-tailwindcss": {
167
- entryPoint: option.entryPoint,
168
- tailwindConfig: option.tailwindConfig
169
- } }
170
- };
171
- })];
186
+ if (typeof option === "object") {
187
+ if (!hasAllPackages(BETTER_TAILWIND_PACKAGES)) return [];
188
+ return [(0, antfu_exports.interopDefault)(import("eslint-plugin-better-tailwindcss")).then((eslintPluginBetterTailwindcss) => {
189
+ const betterTailwindcssRules = {
190
+ ...eslintPluginBetterTailwindcss.configs["recommended-warn"].rules,
191
+ ...eslintPluginBetterTailwindcss.configs["recommended-error"].rules
192
+ };
193
+ return {
194
+ name: "icebreaker/better-tailwindcss",
195
+ plugins: { "better-tailwindcss": eslintPluginBetterTailwindcss },
196
+ rules: betterTailwindcssRules,
197
+ settings: { "better-tailwindcss": {
198
+ entryPoint: option.entryPoint,
199
+ tailwindConfig: option.tailwindConfig
200
+ } }
201
+ };
202
+ })];
203
+ }
204
+ if (!hasAllPackages(TAILWIND_PACKAGES)) return [];
172
205
  return [(0, antfu_exports.interopDefault)(import("eslint-plugin-tailwindcss")).then((tailwind) => {
173
206
  return tailwind.configs["flat/recommended"];
174
207
  }), { rules: { "tailwindcss/no-custom-classname": "off" } }];
@@ -183,6 +216,7 @@ function resolveStylelintBridgeOptions(option) {
183
216
  }
184
217
  function resolveStylelintBridgePresets(option) {
185
218
  if (!option) return [];
219
+ if (!hasAllPackages(STYLELINT_BRIDGE_PACKAGES)) return [];
186
220
  const pluginModulePromise = import("eslint-plugin-better-stylelint");
187
221
  const stylelintOptions = resolveStylelintBridgeOptions(option);
188
222
  return [
@@ -211,6 +245,7 @@ function resolveStylelintBridgePresets(option) {
211
245
  }
212
246
  function resolveMdxPresets(isEnabled) {
213
247
  if (!isEnabled) return [];
248
+ if (!hasAllPackages(MDX_PACKAGES)) return [];
214
249
  return [(0, antfu_exports.interopDefault)(import("eslint-plugin-mdx")).then((mdx) => {
215
250
  return [{
216
251
  ...mdx.flat,
@@ -227,10 +262,10 @@ function resolveMdxPresets(isEnabled) {
227
262
  function resolveAccessibilityPresets(isEnabled, vueOption, reactOption) {
228
263
  if (!isEnabled) return [];
229
264
  const presets = [];
230
- if (vueOption) presets.push((0, antfu_exports.interopDefault)(import("eslint-plugin-vuejs-accessibility")).then((pluginVueA11y) => {
265
+ if (vueOption && hasAllPackages(VUE_A11Y_PACKAGES)) presets.push((0, antfu_exports.interopDefault)(import("eslint-plugin-vuejs-accessibility")).then((pluginVueA11y) => {
231
266
  return pluginVueA11y.configs["flat/recommended"];
232
267
  }));
233
- if (reactOption) presets.push((0, antfu_exports.interopDefault)(import("eslint-plugin-jsx-a11y")).then((jsxA11y) => {
268
+ if (reactOption && hasAllPackages(REACT_A11Y_PACKAGES)) presets.push((0, antfu_exports.interopDefault)(import("eslint-plugin-jsx-a11y")).then((jsxA11y) => {
234
269
  return jsxA11y.flatConfigs.recommended;
235
270
  }));
236
271
  return presets;
@@ -244,6 +279,7 @@ function resolveNestPresets(isEnabled) {
244
279
  }
245
280
  function resolveQueryPresets(isEnabled) {
246
281
  if (!isEnabled) return [];
282
+ if (!hasAllPackages(QUERY_PACKAGES)) return [];
247
283
  return [(0, antfu_exports.interopDefault)(import("@tanstack/eslint-plugin-query")).then((pluginQuery) => pluginQuery.configs["flat/recommended"])];
248
284
  }
249
285
  //#endregion
@@ -287,11 +323,6 @@ createDefu((object, key, currentValue) => {
287
323
  }
288
324
  });
289
325
  //#endregion
290
- //#region src/utils.ts
291
- function isObject(o) {
292
- return Object.prototype.toString.call(o) === "[object Object]";
293
- }
294
- //#endregion
295
326
  //#region src/options.ts
296
327
  const BASE_DEFAULTS = {
297
328
  formatters: true,
@@ -450,6 +481,10 @@ function resolveFormattersOption(input, cwd = node_process.default.cwd()) {
450
481
  }
451
482
  function resolveUserOptions(options) {
452
483
  const resolved = defu({}, options ?? {}, BASE_DEFAULTS);
484
+ if (isMiniProgramEnabled(options)) {
485
+ resolved.miniProgram = true;
486
+ delete resolved.weapp;
487
+ }
453
488
  const resolvedVue = mergeOptionWithDefaults(resolved.vue, getDefaultVueOptions(options), { postProcess: applyVueVersionSpecificRules });
454
489
  if (resolvedVue === void 0) delete resolved.vue;
455
490
  else resolved.vue = resolvedVue;
@@ -468,22 +503,63 @@ function createBaseRuleSet(isLegacy) {
468
503
  }
469
504
  //#endregion
470
505
  //#region src/preset.ts
506
+ const MINI_PROGRAM_IGNORES = [
507
+ "dist/**",
508
+ ".weapp-vite/**",
509
+ "miniprogram_npm/**",
510
+ "node_modules/**",
511
+ "project.config.json",
512
+ "project.private.config.json"
513
+ ];
514
+ const MINI_PROGRAM_GLOBALS = {
515
+ wx: "readonly",
516
+ Page: "readonly",
517
+ App: "readonly",
518
+ Component: "readonly",
519
+ getApp: "readonly",
520
+ getCurrentPages: "readonly",
521
+ requirePlugin: "readonly",
522
+ WechatMiniprogram: "readonly"
523
+ };
471
524
  function getPresets(options, mode) {
472
525
  const resolved = resolveUserOptions(options);
473
- const presets = [{ rules: createBaseRuleSet(mode === "legacy") }, {
474
- files: ["**/*.{css,scss,sass,less,pcss,postcss,json,jsonc,json5}"],
475
- rules: { "style/eol-last": "off" }
476
- }];
526
+ const presets = [
527
+ ...resolved.miniProgram ? [{ ignores: [...MINI_PROGRAM_IGNORES] }, {
528
+ files: ["**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx,vue}"],
529
+ languageOptions: { globals: MINI_PROGRAM_GLOBALS }
530
+ }] : [],
531
+ { rules: createBaseRuleSet(mode === "legacy") },
532
+ {
533
+ files: ["**/*.{css,scss,sass,less,pcss,postcss,json,jsonc,json5}"],
534
+ rules: { "style/eol-last": "off" }
535
+ }
536
+ ];
477
537
  presets.push(...resolveStylelintBridgePresets(resolved.stylelint), ...resolveTailwindPresets(resolved.tailwindcss), ...resolveMdxPresets(resolved.mdx), ...resolveNestPresets(resolved.nestjs), ...resolveQueryPresets(resolved.query), ...resolveAccessibilityPresets(resolved.a11y, resolved.vue, resolved.react));
478
538
  return [resolved, ...presets];
479
539
  }
480
540
  //#endregion
481
541
  //#region src/factory.ts
542
+ const OPTIONAL_ANTFU_FEATURE_PACKAGES = {
543
+ react: [
544
+ "@eslint-react/eslint-plugin",
545
+ "eslint-plugin-react-hooks",
546
+ "eslint-plugin-react-refresh"
547
+ ],
548
+ nextjs: ["@next/eslint-plugin-next"]
549
+ };
550
+ function normalizeOptionalAntfuFeatures(options) {
551
+ const normalized = { ...options };
552
+ if (normalized.react && !hasAllPackages([...OPTIONAL_ANTFU_FEATURE_PACKAGES.react])) normalized.react = false;
553
+ if (normalized.nextjs && !hasAllPackages([...OPTIONAL_ANTFU_FEATURE_PACKAGES.nextjs])) normalized.nextjs = false;
554
+ return normalized;
555
+ }
482
556
  function icebreaker(options = {}, ...userConfigs) {
483
- return (0, antfu_exports.antfu)(...getPresets(options), ...userConfigs);
557
+ const [resolved, ...presets] = getPresets(options);
558
+ return (0, antfu_exports.antfu)(normalizeOptionalAntfuFeatures(resolved), ...presets, ...userConfigs);
484
559
  }
485
560
  function icebreakerLegacy(options = {}, ...userConfigs) {
486
- return (0, antfu_exports.antfu)(...getPresets(options, "legacy"), ...userConfigs);
561
+ const [resolved, ...presets] = getPresets(options, "legacy");
562
+ return (0, antfu_exports.antfu)(normalizeOptionalAntfuFeatures(resolved), ...presets, ...userConfigs);
487
563
  }
488
564
  //#endregion
489
565
  exports.__toESM = __toESM;
package/dist/index.d.cts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Awaitable, ConfigNames, OptionsConfig, TypedFlatConfigItem, TypedFlatConfigItem as TypedFlatConfigItem$1 } from "@antfu/eslint-config";
2
+ import { IcebreakerStylelintOptions } from "@icebreakers/stylelint-config";
2
3
 
3
4
  //#region ../../node_modules/.pnpm/@types+json-schema@7.0.15/node_modules/@types/json-schema/index.d.ts
4
5
  // ==================================================================================================
@@ -2956,11 +2957,16 @@ interface TailwindcssOption {
2956
2957
  tailwindConfig?: string;
2957
2958
  }
2958
2959
  type TailwindcssConfig = boolean | TailwindcssOption;
2959
- interface StylelintBridgeOption {
2960
+ interface StylelintBridgeOption extends IcebreakerStylelintOptions {
2960
2961
  cwd?: string;
2961
2962
  }
2962
2963
  type StylelintBridgeConfig = boolean | StylelintBridgeOption;
2963
2964
  type UserDefinedOptions = OptionsConfig & TypedFlatConfigItem$1 & {
2965
+ /**
2966
+ * Enable Mini Program support.
2967
+ * @default false
2968
+ */
2969
+ miniProgram?: boolean;
2964
2970
  /**
2965
2971
  * Enable TailwindCSS support
2966
2972
  * @default false
@@ -2998,6 +3004,7 @@ type UserDefinedOptions = OptionsConfig & TypedFlatConfigItem$1 & {
2998
3004
  ionic?: boolean;
2999
3005
  /**
3000
3006
  * Enable Weapp support
3007
+ * @deprecated Use `miniProgram` instead.
3001
3008
  * @default false
3002
3009
  */
3003
3010
  weapp?: boolean;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Awaitable, ConfigNames, OptionsConfig, TypedFlatConfigItem, TypedFlatConfigItem as TypedFlatConfigItem$1 } from "@antfu/eslint-config";
2
+ import { IcebreakerStylelintOptions } from "@icebreakers/stylelint-config";
2
3
 
3
4
  //#region ../../node_modules/.pnpm/@types+json-schema@7.0.15/node_modules/@types/json-schema/index.d.ts
4
5
  // ==================================================================================================
@@ -2956,11 +2957,16 @@ interface TailwindcssOption {
2956
2957
  tailwindConfig?: string;
2957
2958
  }
2958
2959
  type TailwindcssConfig = boolean | TailwindcssOption;
2959
- interface StylelintBridgeOption {
2960
+ interface StylelintBridgeOption extends IcebreakerStylelintOptions {
2960
2961
  cwd?: string;
2961
2962
  }
2962
2963
  type StylelintBridgeConfig = boolean | StylelintBridgeOption;
2963
2964
  type UserDefinedOptions = OptionsConfig & TypedFlatConfigItem$1 & {
2965
+ /**
2966
+ * Enable Mini Program support.
2967
+ * @default false
2968
+ */
2969
+ miniProgram?: boolean;
2964
2970
  /**
2965
2971
  * Enable TailwindCSS support
2966
2972
  * @default false
@@ -2998,6 +3004,7 @@ type UserDefinedOptions = OptionsConfig & TypedFlatConfigItem$1 & {
2998
3004
  ionic?: boolean;
2999
3005
  /**
3000
3006
  * Enable Weapp support
3007
+ * @deprecated Use `miniProgram` instead.
3001
3008
  * @default false
3002
3009
  */
3003
3010
  weapp?: boolean;
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { createRequire } from "node:module";
2
- import fs from "node:fs";
3
2
  import path from "node:path";
4
3
  import process from "node:process";
4
+ import { fileURLToPath } from "node:url";
5
+ import fs from "node:fs";
5
6
  //#region \0rolldown/runtime.js
6
7
  var __defProp = Object.defineProperty;
7
8
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -95,6 +96,9 @@ const nestjsTypeScriptRules = {
95
96
  types: { Function: false }
96
97
  }]
97
98
  };
99
+ function isMiniProgramEnabled(opts) {
100
+ return opts?.miniProgram === true || opts?.weapp === true;
101
+ }
98
102
  function getDefaultVueOptions(opts) {
99
103
  const overrides = {
100
104
  "vue/attribute-hyphenation": "off",
@@ -108,7 +112,7 @@ function getDefaultVueOptions(opts) {
108
112
  "vue/no-unused-refs": "warn"
109
113
  };
110
114
  if (opts?.ionic) overrides["vue/no-deprecated-slot-attribute"] = "off";
111
- if (opts?.weapp) overrides["vue/singleline-html-element-content-newline"] = ["warn", {
115
+ if (isMiniProgramEnabled(opts)) overrides["vue/singleline-html-element-content-newline"] = ["warn", {
112
116
  ignoreWhenNoAttributes: true,
113
117
  ignoreWhenEmpty: true,
114
118
  ignores: ["text", ...INLINE_ELEMENTS],
@@ -139,27 +143,56 @@ function getDefaultTypescriptOptions(opts) {
139
143
  return { overrides };
140
144
  }
141
145
  //#endregion
146
+ //#region src/utils.ts
147
+ const require$1 = createRequire(import.meta.url);
148
+ const PACKAGE_DIR = path.dirname(fileURLToPath(import.meta.url));
149
+ function isObject(o) {
150
+ return Object.prototype.toString.call(o) === "[object Object]";
151
+ }
152
+ function isPackageAvailable$1(name, searchPaths = [process.cwd(), PACKAGE_DIR]) {
153
+ try {
154
+ require$1.resolve(name, { paths: searchPaths });
155
+ return true;
156
+ } catch {
157
+ return false;
158
+ }
159
+ }
160
+ function hasAllPackages(names, searchPaths) {
161
+ return names.every((name) => isPackageAvailable$1(name, searchPaths));
162
+ }
163
+ //#endregion
142
164
  //#region src/features.ts
165
+ const BETTER_TAILWIND_PACKAGES = ["eslint-plugin-better-tailwindcss"];
166
+ const TAILWIND_PACKAGES = ["eslint-plugin-tailwindcss"];
167
+ const STYLELINT_BRIDGE_PACKAGES = ["eslint-plugin-better-stylelint"];
168
+ const MDX_PACKAGES = ["eslint-plugin-mdx"];
169
+ const VUE_A11Y_PACKAGES = ["eslint-plugin-vuejs-accessibility"];
170
+ const REACT_A11Y_PACKAGES = ["eslint-plugin-jsx-a11y"];
171
+ const QUERY_PACKAGES = ["@tanstack/eslint-plugin-query"];
143
172
  function resolveStylelintConfigLoader() {
144
173
  return import.meta.url.endsWith(".ts") ? new URL("./stylelint.ts", import.meta.url).href : new URL("./stylelint.js", import.meta.url).href;
145
174
  }
146
175
  function resolveTailwindPresets(option) {
147
176
  if (!option) return [];
148
- if (typeof option === "object") return [(0, antfu_exports.interopDefault)(import("eslint-plugin-better-tailwindcss")).then((eslintPluginBetterTailwindcss) => {
149
- const betterTailwindcssRules = {
150
- ...eslintPluginBetterTailwindcss.configs["recommended-warn"].rules,
151
- ...eslintPluginBetterTailwindcss.configs["recommended-error"].rules
152
- };
153
- return {
154
- name: "icebreaker/better-tailwindcss",
155
- plugins: { "better-tailwindcss": eslintPluginBetterTailwindcss },
156
- rules: betterTailwindcssRules,
157
- settings: { "better-tailwindcss": {
158
- entryPoint: option.entryPoint,
159
- tailwindConfig: option.tailwindConfig
160
- } }
161
- };
162
- })];
177
+ if (typeof option === "object") {
178
+ if (!hasAllPackages(BETTER_TAILWIND_PACKAGES)) return [];
179
+ return [(0, antfu_exports.interopDefault)(import("eslint-plugin-better-tailwindcss")).then((eslintPluginBetterTailwindcss) => {
180
+ const betterTailwindcssRules = {
181
+ ...eslintPluginBetterTailwindcss.configs["recommended-warn"].rules,
182
+ ...eslintPluginBetterTailwindcss.configs["recommended-error"].rules
183
+ };
184
+ return {
185
+ name: "icebreaker/better-tailwindcss",
186
+ plugins: { "better-tailwindcss": eslintPluginBetterTailwindcss },
187
+ rules: betterTailwindcssRules,
188
+ settings: { "better-tailwindcss": {
189
+ entryPoint: option.entryPoint,
190
+ tailwindConfig: option.tailwindConfig
191
+ } }
192
+ };
193
+ })];
194
+ }
195
+ if (!hasAllPackages(TAILWIND_PACKAGES)) return [];
163
196
  return [(0, antfu_exports.interopDefault)(import("eslint-plugin-tailwindcss")).then((tailwind) => {
164
197
  return tailwind.configs["flat/recommended"];
165
198
  }), { rules: { "tailwindcss/no-custom-classname": "off" } }];
@@ -174,6 +207,7 @@ function resolveStylelintBridgeOptions(option) {
174
207
  }
175
208
  function resolveStylelintBridgePresets(option) {
176
209
  if (!option) return [];
210
+ if (!hasAllPackages(STYLELINT_BRIDGE_PACKAGES)) return [];
177
211
  const pluginModulePromise = import("eslint-plugin-better-stylelint");
178
212
  const stylelintOptions = resolveStylelintBridgeOptions(option);
179
213
  return [
@@ -202,6 +236,7 @@ function resolveStylelintBridgePresets(option) {
202
236
  }
203
237
  function resolveMdxPresets(isEnabled) {
204
238
  if (!isEnabled) return [];
239
+ if (!hasAllPackages(MDX_PACKAGES)) return [];
205
240
  return [(0, antfu_exports.interopDefault)(import("eslint-plugin-mdx")).then((mdx) => {
206
241
  return [{
207
242
  ...mdx.flat,
@@ -218,10 +253,10 @@ function resolveMdxPresets(isEnabled) {
218
253
  function resolveAccessibilityPresets(isEnabled, vueOption, reactOption) {
219
254
  if (!isEnabled) return [];
220
255
  const presets = [];
221
- if (vueOption) presets.push((0, antfu_exports.interopDefault)(import("eslint-plugin-vuejs-accessibility")).then((pluginVueA11y) => {
256
+ if (vueOption && hasAllPackages(VUE_A11Y_PACKAGES)) presets.push((0, antfu_exports.interopDefault)(import("eslint-plugin-vuejs-accessibility")).then((pluginVueA11y) => {
222
257
  return pluginVueA11y.configs["flat/recommended"];
223
258
  }));
224
- if (reactOption) presets.push((0, antfu_exports.interopDefault)(import("eslint-plugin-jsx-a11y")).then((jsxA11y) => {
259
+ if (reactOption && hasAllPackages(REACT_A11Y_PACKAGES)) presets.push((0, antfu_exports.interopDefault)(import("eslint-plugin-jsx-a11y")).then((jsxA11y) => {
225
260
  return jsxA11y.flatConfigs.recommended;
226
261
  }));
227
262
  return presets;
@@ -235,6 +270,7 @@ function resolveNestPresets(isEnabled) {
235
270
  }
236
271
  function resolveQueryPresets(isEnabled) {
237
272
  if (!isEnabled) return [];
273
+ if (!hasAllPackages(QUERY_PACKAGES)) return [];
238
274
  return [(0, antfu_exports.interopDefault)(import("@tanstack/eslint-plugin-query")).then((pluginQuery) => pluginQuery.configs["flat/recommended"])];
239
275
  }
240
276
  //#endregion
@@ -278,11 +314,6 @@ createDefu((object, key, currentValue) => {
278
314
  }
279
315
  });
280
316
  //#endregion
281
- //#region src/utils.ts
282
- function isObject(o) {
283
- return Object.prototype.toString.call(o) === "[object Object]";
284
- }
285
- //#endregion
286
317
  //#region src/options.ts
287
318
  const BASE_DEFAULTS = {
288
319
  formatters: true,
@@ -441,6 +472,10 @@ function resolveFormattersOption(input, cwd = process.cwd()) {
441
472
  }
442
473
  function resolveUserOptions(options) {
443
474
  const resolved = defu({}, options ?? {}, BASE_DEFAULTS);
475
+ if (isMiniProgramEnabled(options)) {
476
+ resolved.miniProgram = true;
477
+ delete resolved.weapp;
478
+ }
444
479
  const resolvedVue = mergeOptionWithDefaults(resolved.vue, getDefaultVueOptions(options), { postProcess: applyVueVersionSpecificRules });
445
480
  if (resolvedVue === void 0) delete resolved.vue;
446
481
  else resolved.vue = resolvedVue;
@@ -459,22 +494,63 @@ function createBaseRuleSet(isLegacy) {
459
494
  }
460
495
  //#endregion
461
496
  //#region src/preset.ts
497
+ const MINI_PROGRAM_IGNORES = [
498
+ "dist/**",
499
+ ".weapp-vite/**",
500
+ "miniprogram_npm/**",
501
+ "node_modules/**",
502
+ "project.config.json",
503
+ "project.private.config.json"
504
+ ];
505
+ const MINI_PROGRAM_GLOBALS = {
506
+ wx: "readonly",
507
+ Page: "readonly",
508
+ App: "readonly",
509
+ Component: "readonly",
510
+ getApp: "readonly",
511
+ getCurrentPages: "readonly",
512
+ requirePlugin: "readonly",
513
+ WechatMiniprogram: "readonly"
514
+ };
462
515
  function getPresets(options, mode) {
463
516
  const resolved = resolveUserOptions(options);
464
- const presets = [{ rules: createBaseRuleSet(mode === "legacy") }, {
465
- files: ["**/*.{css,scss,sass,less,pcss,postcss,json,jsonc,json5}"],
466
- rules: { "style/eol-last": "off" }
467
- }];
517
+ const presets = [
518
+ ...resolved.miniProgram ? [{ ignores: [...MINI_PROGRAM_IGNORES] }, {
519
+ files: ["**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx,vue}"],
520
+ languageOptions: { globals: MINI_PROGRAM_GLOBALS }
521
+ }] : [],
522
+ { rules: createBaseRuleSet(mode === "legacy") },
523
+ {
524
+ files: ["**/*.{css,scss,sass,less,pcss,postcss,json,jsonc,json5}"],
525
+ rules: { "style/eol-last": "off" }
526
+ }
527
+ ];
468
528
  presets.push(...resolveStylelintBridgePresets(resolved.stylelint), ...resolveTailwindPresets(resolved.tailwindcss), ...resolveMdxPresets(resolved.mdx), ...resolveNestPresets(resolved.nestjs), ...resolveQueryPresets(resolved.query), ...resolveAccessibilityPresets(resolved.a11y, resolved.vue, resolved.react));
469
529
  return [resolved, ...presets];
470
530
  }
471
531
  //#endregion
472
532
  //#region src/factory.ts
533
+ const OPTIONAL_ANTFU_FEATURE_PACKAGES = {
534
+ react: [
535
+ "@eslint-react/eslint-plugin",
536
+ "eslint-plugin-react-hooks",
537
+ "eslint-plugin-react-refresh"
538
+ ],
539
+ nextjs: ["@next/eslint-plugin-next"]
540
+ };
541
+ function normalizeOptionalAntfuFeatures(options) {
542
+ const normalized = { ...options };
543
+ if (normalized.react && !hasAllPackages([...OPTIONAL_ANTFU_FEATURE_PACKAGES.react])) normalized.react = false;
544
+ if (normalized.nextjs && !hasAllPackages([...OPTIONAL_ANTFU_FEATURE_PACKAGES.nextjs])) normalized.nextjs = false;
545
+ return normalized;
546
+ }
473
547
  function icebreaker(options = {}, ...userConfigs) {
474
- return (0, antfu_exports.antfu)(...getPresets(options), ...userConfigs);
548
+ const [resolved, ...presets] = getPresets(options);
549
+ return (0, antfu_exports.antfu)(normalizeOptionalAntfuFeatures(resolved), ...presets, ...userConfigs);
475
550
  }
476
551
  function icebreakerLegacy(options = {}, ...userConfigs) {
477
- return (0, antfu_exports.antfu)(...getPresets(options, "legacy"), ...userConfigs);
552
+ const [resolved, ...presets] = getPresets(options, "legacy");
553
+ return (0, antfu_exports.antfu)(normalizeOptionalAntfuFeatures(resolved), ...presets, ...userConfigs);
478
554
  }
479
555
  //#endregion
480
556
  export { getPresets, icebreaker, icebreakerLegacy };
package/index.d.ts CHANGED
@@ -4,6 +4,7 @@ import type {
4
4
  OptionsConfig,
5
5
  TypedFlatConfigItem,
6
6
  } from '@antfu/eslint-config'
7
+ import type { IcebreakerStylelintOptions } from '@icebreakers/stylelint-config'
7
8
  import type { Linter } from 'eslint'
8
9
  import type { FlatConfigComposer } from 'eslint-flat-config-utils'
9
10
 
@@ -13,12 +14,13 @@ export interface TailwindcssOption {
13
14
  }
14
15
 
15
16
  export type TailwindcssConfig = boolean | TailwindcssOption
16
- export interface StylelintBridgeOption {
17
+ export interface StylelintBridgeOption extends IcebreakerStylelintOptions {
17
18
  cwd?: string
18
19
  }
19
20
  export type StylelintBridgeConfig = boolean | StylelintBridgeOption
20
21
 
21
22
  export type UserDefinedOptions = OptionsConfig & TypedFlatConfigItem & {
23
+ miniProgram?: boolean
22
24
  tailwindcss?: TailwindcssConfig
23
25
  stylelint?: StylelintBridgeConfig
24
26
  mdx?: boolean
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@icebreakers/eslint-config",
3
3
  "type": "module",
4
- "version": "2.0.3",
4
+ "version": "2.1.1",
5
5
  "description": "ESLint preset from Icebreaker's dev-configs",
6
6
  "author": "ice breaker <1324318532@qq.com>",
7
7
  "license": "MIT",
@@ -48,22 +48,22 @@
48
48
  },
49
49
  "dependencies": {
50
50
  "@antfu/eslint-config": "7.7.3",
51
- "@eslint-react/eslint-plugin": "^3.0.0",
52
- "@next/eslint-plugin-next": "^16.2.1",
53
- "@tanstack/eslint-plugin-query": "^5.95.2",
54
51
  "eslint-plugin-better-tailwindcss": "^4.3.2",
55
52
  "eslint-plugin-format": "2.0.1",
56
- "eslint-plugin-jsx-a11y": "^6.10.2",
57
- "eslint-plugin-react-hooks": "^7.0.1",
58
- "eslint-plugin-react-refresh": "^0.5.2",
59
53
  "eslint-plugin-tailwindcss": "3.18.2",
60
54
  "eslint-plugin-vuejs-accessibility": "^2.5.0",
61
- "@icebreakers/stylelint-config": "2.1.2",
55
+ "@icebreakers/stylelint-config": "2.2.1",
62
56
  "eslint-plugin-better-stylelint": "0.1.1"
63
57
  },
64
58
  "optionalDependencies": {
59
+ "@eslint-react/eslint-plugin": "^3.0.0",
60
+ "@next/eslint-plugin-next": "^16.2.1",
61
+ "@tanstack/eslint-plugin-query": "^5.95.2",
65
62
  "@unocss/eslint-plugin": "66.6.7",
66
- "eslint-plugin-mdx": "3.7.0"
63
+ "eslint-plugin-jsx-a11y": "^6.10.2",
64
+ "eslint-plugin-mdx": "3.7.0",
65
+ "eslint-plugin-react-hooks": "^7.0.1",
66
+ "eslint-plugin-react-refresh": "^0.5.2"
67
67
  },
68
68
  "publishConfig": {
69
69
  "access": "public",
@@ -72,8 +72,16 @@
72
72
  "tsd": {
73
73
  "directory": "test-d",
74
74
  "compilerOptions": {
75
- "module": "esnext",
76
- "moduleResolution": "bundler",
75
+ "module": "nodenext",
76
+ "baseUrl": ".",
77
+ "paths": {
78
+ "@icebreakers/stylelint-config": [
79
+ "../stylelint/dist/index.d.ts"
80
+ ],
81
+ "eslint-plugin-better-stylelint": [
82
+ "../eslint-plugin-better-stylelint/dist/index.d.ts"
83
+ ]
84
+ },
77
85
  "skipLibCheck": true
78
86
  }
79
87
  },