@diphyx/eslint-plugin 1.0.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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +130 -0
  3. package/package.json +55 -0
  4. package/src/configs/index.mjs +11 -0
  5. package/src/configs/recommended.mjs +184 -0
  6. package/src/index.mjs +22 -0
  7. package/src/rules/composable-naming.mjs +63 -0
  8. package/src/rules/index.mjs +105 -0
  9. package/src/rules/radash-prefer-call.mjs +33 -0
  10. package/src/rules/radash-prefer-clone.mjs +33 -0
  11. package/src/rules/radash-prefer-is.mjs +51 -0
  12. package/src/rules/radash-prefer-sleep.mjs +53 -0
  13. package/src/rules/radash-prefer-sum.mjs +71 -0
  14. package/src/rules/radash-prefer-unique.mjs +30 -0
  15. package/src/rules/script-define-object.mjs +49 -0
  16. package/src/rules/script-section-order.mjs +159 -0
  17. package/src/rules/store-config-order.mjs +55 -0
  18. package/src/rules/store-mode-enum.mjs +41 -0
  19. package/src/rules/store-name-match.mjs +56 -0
  20. package/src/rules/store-no-unknown-key.mjs +42 -0
  21. package/src/rules/store-require-action.mjs +5 -0
  22. package/src/rules/store-require-model.mjs +5 -0
  23. package/src/rules/store-require-name.mjs +5 -0
  24. package/src/rules/store-require-view.mjs +5 -0
  25. package/src/rules/store-section-function.mjs +47 -0
  26. package/src/rules/store-section-method.mjs +53 -0
  27. package/src/rules/store-section-return-shorthand.mjs +81 -0
  28. package/src/rules/store-shape-suffix.mjs +37 -0
  29. package/src/rules/store-suffix.mjs +39 -0
  30. package/src/rules/template-text.mjs +80 -0
  31. package/src/rules/template-v-else.mjs +8 -0
  32. package/src/rules/template-v-for.mjs +8 -0
  33. package/src/rules/template-v-if.mjs +8 -0
  34. package/src/rules/vueuse-prefer-clipboard.mjs +29 -0
  35. package/src/rules/vueuse-prefer-member-call.mjs +39 -0
  36. package/src/rules/vueuse-prefer-observer.mjs +34 -0
  37. package/src/rules/vueuse-prefer-route.mjs +64 -0
  38. package/src/rules/vueuse-prefer-storage.mjs +44 -0
  39. package/src/rules/vueuse-prefer-timer.mjs +32 -0
  40. package/src/utils/ast.mjs +24 -0
  41. package/src/utils/docs.mjs +8 -0
  42. package/src/utils/store.mjs +66 -0
  43. package/src/utils/vue.mjs +70 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DiPhyx
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # @diphyx/eslint-plugin
2
+
3
+ Opinionated ESLint rules and a ready-to-use flat config that capture DiPhyx's Nuxt/Vue + [Harlemify](https://github.com/diphyx/harlemify) conventions — so every DiPhyx app lints the same way from a single dependency.
4
+
5
+ A stock `eslint-plugin-vue` setup checks general Vue style; this plugin adds the project-specific patterns it can't know about — where template directives belong, how `<script setup>` is ordered and written, the exact shape of a Harlemify `createStore`, composable naming, and reaching for radash / VueUse instead of hand-rolled code.
6
+
7
+ ## Highlights
8
+
9
+ - **32 custom rules** covering SFC templates, `<script setup>` structure, Harlemify stores, composable naming, and radash / VueUse usage.
10
+ - **One-line preset** — `configs.recommended` wires up the TypeScript + Vue parsers, the relevant `eslint-plugin-vue` rules, file-naming, and every custom rule.
11
+ - **No extra peer deps** — the parsers and plugins ship inside this package; you only install `eslint` itself.
12
+ - **Guidance, not gates** — every rule reports as a warning and none auto-fix, so it nudges without blocking commits.
13
+ - **Modern toolchain** — flat config, ESLint 9+, ESM only.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ pnpm add -D @diphyx/eslint-plugin eslint
19
+ ```
20
+
21
+ The plugin ships its own parser/plugin dependencies (`@typescript-eslint/*`, `vue-eslint-parser`, `eslint-plugin-vue`, `eslint-plugin-check-file`), so you only need `eslint` itself as a peer.
22
+
23
+ ## Usage
24
+
25
+ `eslint.config.mjs`:
26
+
27
+ ```js
28
+ import diphyx from "@diphyx/eslint-plugin";
29
+
30
+ export default [
31
+ ...diphyx.configs.recommended,
32
+
33
+ // project-specific overrides / ignores
34
+ {
35
+ ignores: ["node_modules/**", ".nuxt/**", ".output/**", "dist/**", "**/*.d.ts"],
36
+ },
37
+ ];
38
+ ```
39
+
40
+ ### Picking rules à la carte
41
+
42
+ If you don't want the preset, register the plugin and turn on rules yourself:
43
+
44
+ ```js
45
+ import diphyx from "@diphyx/eslint-plugin";
46
+
47
+ export default [
48
+ {
49
+ files: ["**/*.vue"],
50
+ plugins: { "@diphyx": diphyx },
51
+ rules: {
52
+ "@diphyx/template-v-if": "warn",
53
+ "@diphyx/template-text": "warn",
54
+ },
55
+ },
56
+ ];
57
+ ```
58
+
59
+ ## Rules
60
+
61
+ All rules are report-only (warnings); none auto-fix. Each rule has its own page
62
+ under [`docs/rules/`](./docs/rules) (also linked from the rule's `meta.docs.url`).
63
+
64
+ ### Template (`*.vue`)
65
+
66
+ | Rule | Enforces |
67
+ | ----------------- | -------------------------------------------------------- |
68
+ | `template-v-if` | `v-if` must be on a `<template>` wrapper |
69
+ | `template-v-else` | `v-else` / `v-else-if` must be on a `<template>` wrapper |
70
+ | `template-v-for` | `v-for` must be on a `<template>` wrapper |
71
+ | `template-text` | bare text must be wrapped in an HTML tag |
72
+
73
+ ### Script (`*.vue`)
74
+
75
+ | Rule | Enforces |
76
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
77
+ | `script-section-order` | script-setup section order: import → props → model → emit → composable → state → computed → watch → method → lifecycle → expose |
78
+ | `script-define-object` | `define*` macros must declare their shape with a runtime object, not the type-only form |
79
+
80
+ ### Store — Harlemify `createStore`
81
+
82
+ | Rule | Enforces |
83
+ | --------------------------------------------------------------------- | ---------------------------------------------------------- |
84
+ | `store-require-name` | config has a required `name` property |
85
+ | `store-require-model` / `store-require-view` / `store-require-action` | required sections are present |
86
+ | `store-section-function` | sections are functions |
87
+ | `store-section-method` | sections use method shorthand (not arrow functions) |
88
+ | `store-section-return-shorthand` | sections return a shorthand object of named consts |
89
+ | `store-config-order` | key order: name → model → view → action → compose → lazy |
90
+ | `store-no-unknown-key` | no unknown config keys |
91
+ | `store-suffix` | store variable ends in `Store` |
92
+ | `store-name-match` | `name` matches the variable (`accountStore` → `"account"`) |
93
+ | `store-shape-suffix` | `shape()` result is named `*Shape` |
94
+ | `store-mode-enum` | `mode` uses a `ModelMode` enum, not a string literal |
95
+
96
+ ### Composable (`app/composables/*.ts`)
97
+
98
+ | Rule | Enforces |
99
+ | ------------------- | ------------------------------------ |
100
+ | `composable-naming` | exported composables follow `useXxx` |
101
+
102
+ ### Radash preferences (`*.ts`, `*.vue`)
103
+
104
+ Prefer radash helpers over hand-rolled equivalents.
105
+
106
+ | Rule | Enforces |
107
+ | ---------------------- | ------------------------------------------------------------------------------- |
108
+ | `radash-prefer-is` | prefer radash `is*` helpers over `typeof` comparisons |
109
+ | `radash-prefer-call` | prefer radash helpers over native global calls (`Promise.all`, `Array.isArray`) |
110
+ | `radash-prefer-clone` | prefer `clone()` over `JSON.parse(JSON.stringify())` |
111
+ | `radash-prefer-unique` | prefer `unique()` over spreading a `new Set()` |
112
+ | `radash-prefer-sum` | prefer `sum()` over a `reduce` that adds values |
113
+ | `radash-prefer-sleep` | prefer `sleep()` over wrapping `setTimeout` in a Promise |
114
+
115
+ ### VueUse preferences (`*.ts`, `*.vue`)
116
+
117
+ Prefer VueUse composables (with automatic lifecycle cleanup) over raw browser APIs.
118
+
119
+ | Rule | Enforces |
120
+ | --------------------------- | ---------------------------------------------------------------------------------------------------- |
121
+ | `vueuse-prefer-storage` | prefer `useLocalStorage` / `useSessionStorage` over raw Web Storage |
122
+ | `vueuse-prefer-member-call` | prefer VueUse composables over raw DOM/window method calls (`addEventListener`, `matchMedia`) |
123
+ | `vueuse-prefer-timer` | prefer VueUse timer composables over native timers (`setInterval`) |
124
+ | `vueuse-prefer-observer` | prefer VueUse observer composables over raw observers |
125
+ | `vueuse-prefer-clipboard` | prefer `useClipboard()` over `navigator.clipboard` |
126
+ | `vueuse-prefer-route` | prefer `useRouteQuery` / `useRouteParams` / `useRouteHash` over `useRoute().query`/`.params`/`.hash` |
127
+
128
+ ## License
129
+
130
+ MIT
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@diphyx/eslint-plugin",
3
+ "version": "1.0.0",
4
+ "description": "Shared ESLint plugin and flat config for DiPhyx Nuxt/Vue + Harlemify projects",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": "./src/index.mjs"
9
+ },
10
+ "files": [
11
+ "src"
12
+ ],
13
+ "scripts": {
14
+ "lint": "eslint .",
15
+ "format": "prettier --write .",
16
+ "format:check": "prettier --check ."
17
+ },
18
+ "keywords": [
19
+ "eslint",
20
+ "eslintplugin",
21
+ "eslint-plugin",
22
+ "diphyx",
23
+ "nuxt",
24
+ "vue",
25
+ "harlemify"
26
+ ],
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/diphyx/eslint-plugin.git"
30
+ },
31
+ "homepage": "https://github.com/diphyx/eslint-plugin#readme",
32
+ "bugs": {
33
+ "url": "https://github.com/diphyx/eslint-plugin/issues"
34
+ },
35
+ "dependencies": {
36
+ "@typescript-eslint/eslint-plugin": "^8.61.0",
37
+ "@typescript-eslint/parser": "^8.61.0",
38
+ "eslint-plugin-check-file": "^3.3.1",
39
+ "eslint-plugin-vue": "^10.9.2",
40
+ "vue-eslint-parser": "^10.4.1"
41
+ },
42
+ "peerDependencies": {
43
+ "eslint": ">=9"
44
+ },
45
+ "devDependencies": {
46
+ "eslint": "^10.5.0",
47
+ "prettier": "^3.4.2"
48
+ },
49
+ "engines": {
50
+ "node": ">=18"
51
+ },
52
+ "publishConfig": {
53
+ "access": "public"
54
+ }
55
+ }
@@ -0,0 +1,11 @@
1
+ // Ready-to-use flat config presets. `createConfigs(plugin)` is called from the
2
+ // package entry so each preset can register the plugin under the "@diphyx"
3
+ // namespace.
4
+
5
+ import { createRecommended } from "./recommended.mjs";
6
+
7
+ export function createConfigs(plugin) {
8
+ return {
9
+ recommended: createRecommended(plugin),
10
+ };
11
+ }
@@ -0,0 +1,184 @@
1
+ // The `recommended` flat-config preset: wires the TypeScript and Vue parsers,
2
+ // the relevant eslint-plugin-vue rules, file-naming, and every custom rule.
3
+ // `createRecommended(plugin)` takes the plugin so the preset can register it
4
+ // under the "@diphyx" namespace.
5
+
6
+ import tsParser from "@typescript-eslint/parser";
7
+ import vueParser from "vue-eslint-parser";
8
+
9
+ import tsPlugin from "@typescript-eslint/eslint-plugin";
10
+ import vuePlugin from "eslint-plugin-vue";
11
+ import checkFilePlugin from "eslint-plugin-check-file";
12
+
13
+ const namingConvention = [
14
+ "warn",
15
+ {
16
+ selector: "enum",
17
+ format: ["PascalCase"],
18
+ },
19
+ {
20
+ selector: "enumMember",
21
+ format: ["UPPER_CASE"],
22
+ },
23
+ {
24
+ selector: "typeAlias",
25
+ format: ["PascalCase"],
26
+ },
27
+ {
28
+ selector: "interface",
29
+ format: ["PascalCase"],
30
+ },
31
+ ];
32
+
33
+ export function createRecommended(plugin) {
34
+ return [
35
+ // TypeScript files
36
+ {
37
+ files: ["**/*.ts"],
38
+ languageOptions: {
39
+ parser: tsParser,
40
+ parserOptions: {
41
+ ecmaVersion: "latest",
42
+ sourceType: "module",
43
+ },
44
+ },
45
+ plugins: {
46
+ "@typescript-eslint": tsPlugin,
47
+ },
48
+ rules: {
49
+ "no-unused-vars": "off",
50
+ "no-undef": "off",
51
+ "@typescript-eslint/naming-convention": namingConvention,
52
+ "arrow-body-style": ["warn", "always"],
53
+ },
54
+ },
55
+
56
+ // Vue files
57
+ {
58
+ files: ["**/*.vue"],
59
+ languageOptions: {
60
+ parser: vueParser,
61
+ parserOptions: {
62
+ parser: tsParser,
63
+ ecmaVersion: "latest",
64
+ sourceType: "module",
65
+ extraFileExtensions: [".vue"],
66
+ },
67
+ },
68
+ plugins: {
69
+ vue: vuePlugin,
70
+ "@typescript-eslint": tsPlugin,
71
+ "@diphyx": plugin,
72
+ },
73
+ rules: {
74
+ "no-unused-vars": "off",
75
+ "no-undef": "off",
76
+ "@typescript-eslint/naming-convention": namingConvention,
77
+ "arrow-body-style": ["warn", "always"],
78
+
79
+ // Vue rules
80
+ "vue/block-lang": ["warn", { script: { lang: "ts" } }],
81
+ "vue/component-api-style": ["warn", ["script-setup"]],
82
+ "vue/define-macros-order": "off",
83
+ "vue/attributes-order": [
84
+ "warn",
85
+ {
86
+ order: [
87
+ "DEFINITION",
88
+ "LIST_RENDERING",
89
+ "CONDITIONALS",
90
+ "RENDER_MODIFIERS",
91
+ "GLOBAL",
92
+ "UNIQUE",
93
+ "SLOT",
94
+ "TWO_WAY_BINDING",
95
+ "OTHER_DIRECTIVES",
96
+ "ATTR_DYNAMIC",
97
+ "ATTR_STATIC",
98
+ "EVENTS",
99
+ "CONTENT",
100
+ "ATTR_SHORTHAND_BOOL",
101
+ ],
102
+ alphabetical: false,
103
+ },
104
+ ],
105
+
106
+ // Template + script rules
107
+ "@diphyx/template-v-if": "warn",
108
+ "@diphyx/template-v-else": "warn",
109
+ "@diphyx/template-v-for": "warn",
110
+ "@diphyx/template-text": "warn",
111
+ "@diphyx/script-section-order": "warn",
112
+ "@diphyx/script-define-object": "warn",
113
+ },
114
+ },
115
+
116
+ // Component file naming
117
+ {
118
+ files: ["app/components/**/*.vue"],
119
+ ignores: ["**/index.vue"],
120
+ plugins: {
121
+ "check-file": checkFilePlugin,
122
+ },
123
+ rules: {
124
+ "check-file/filename-naming-convention": ["warn", { "**/*.vue": "PASCAL_CASE" }],
125
+ },
126
+ },
127
+
128
+ // Store pattern
129
+ {
130
+ files: ["app/stores/*.ts", "app/composables/*.ts"],
131
+ plugins: {
132
+ "@diphyx": plugin,
133
+ },
134
+ rules: {
135
+ "@diphyx/store-require-name": "warn",
136
+ "@diphyx/store-require-model": "warn",
137
+ "@diphyx/store-require-view": "warn",
138
+ "@diphyx/store-require-action": "warn",
139
+ "@diphyx/store-section-function": "warn",
140
+ "@diphyx/store-section-method": "warn",
141
+ "@diphyx/store-section-return-shorthand": "warn",
142
+ "@diphyx/store-config-order": "warn",
143
+ "@diphyx/store-no-unknown-key": "warn",
144
+ "@diphyx/store-suffix": "warn",
145
+ "@diphyx/store-name-match": "warn",
146
+ "@diphyx/store-shape-suffix": "warn",
147
+ "@diphyx/store-mode-enum": "warn",
148
+ },
149
+ },
150
+
151
+ // Composable naming
152
+ {
153
+ files: ["app/composables/*.ts"],
154
+ plugins: {
155
+ "@diphyx": plugin,
156
+ },
157
+ rules: {
158
+ "@diphyx/composable-naming": "warn",
159
+ },
160
+ },
161
+
162
+ // Utility preferences (radash / vueuse) — all app code
163
+ {
164
+ files: ["**/*.ts", "**/*.vue"],
165
+ plugins: {
166
+ "@diphyx": plugin,
167
+ },
168
+ rules: {
169
+ "@diphyx/radash-prefer-is": "warn",
170
+ "@diphyx/radash-prefer-call": "warn",
171
+ "@diphyx/radash-prefer-clone": "warn",
172
+ "@diphyx/radash-prefer-unique": "warn",
173
+ "@diphyx/radash-prefer-sum": "warn",
174
+ "@diphyx/radash-prefer-sleep": "warn",
175
+ "@diphyx/vueuse-prefer-storage": "warn",
176
+ "@diphyx/vueuse-prefer-member-call": "warn",
177
+ "@diphyx/vueuse-prefer-timer": "warn",
178
+ "@diphyx/vueuse-prefer-observer": "warn",
179
+ "@diphyx/vueuse-prefer-clipboard": "warn",
180
+ "@diphyx/vueuse-prefer-route": "warn",
181
+ },
182
+ },
183
+ ];
184
+ }
package/src/index.mjs ADDED
@@ -0,0 +1,22 @@
1
+ // @diphyx/eslint-plugin — shared ESLint plugin + flat config for DiPhyx
2
+ // Nuxt/Vue + Harlemify projects.
3
+
4
+ import { createRequire } from "node:module";
5
+
6
+ import { rules } from "./rules/index.mjs";
7
+ import { createConfigs } from "./configs/index.mjs";
8
+
9
+ const require = createRequire(import.meta.url);
10
+ const { name, version } = require("../package.json");
11
+
12
+ const plugin = {
13
+ meta: {
14
+ name,
15
+ version,
16
+ },
17
+ rules,
18
+ };
19
+
20
+ plugin.configs = createConfigs(plugin);
21
+
22
+ export default plugin;
@@ -0,0 +1,63 @@
1
+ // Exported composable functions must follow the useXxx naming pattern.
2
+ //
3
+ // The main composable is the module-level export; its returned methods
4
+ // (load/set/get/reset/start/stop) live inside it and are not linted here.
5
+
6
+ const ALLOWED_PATTERN = /^use[A-Z]/;
7
+
8
+ export default {
9
+ meta: {
10
+ type: "suggestion",
11
+ docs: {
12
+ description: "exported composable functions must follow useXxx naming pattern",
13
+ },
14
+ messages: {
15
+ invalidName: "Exported composable function '{{name}}' should follow useXxx naming pattern.",
16
+ },
17
+ schema: [],
18
+ },
19
+ create(context) {
20
+ function check(idNode) {
21
+ const name = idNode?.name;
22
+ if (!name) {
23
+ return;
24
+ }
25
+
26
+ if (!ALLOWED_PATTERN.test(name)) {
27
+ context.report({
28
+ node: idNode,
29
+ messageId: "invalidName",
30
+ data: {
31
+ name,
32
+ },
33
+ });
34
+ }
35
+ }
36
+
37
+ return {
38
+ ExportNamedDeclaration(node) {
39
+ const declaration = node.declaration;
40
+ if (!declaration) {
41
+ return;
42
+ }
43
+
44
+ // export function useXxx() {}
45
+ if (declaration.type === "FunctionDeclaration") {
46
+ check(declaration.id);
47
+
48
+ return;
49
+ }
50
+
51
+ // export const useXxx = () => {} | function () {}
52
+ if (declaration.type === "VariableDeclaration") {
53
+ for (const declarator of declaration.declarations) {
54
+ const init = declarator.init;
55
+ if (init && (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression")) {
56
+ check(declarator.id);
57
+ }
58
+ }
59
+ }
60
+ },
61
+ };
62
+ },
63
+ };
@@ -0,0 +1,105 @@
1
+ // All custom rules, keyed by their kebab-case name (one rule per file). The key
2
+ // is the name a config references as @diphyx/<key>, and it also drives the rule's
3
+ // generated docs URL, so the two never drift.
4
+
5
+ import { docsUrl } from "../utils/docs.mjs";
6
+
7
+ import templateVIf from "./template-v-if.mjs";
8
+ import templateVElse from "./template-v-else.mjs";
9
+ import templateVFor from "./template-v-for.mjs";
10
+ import templateText from "./template-text.mjs";
11
+
12
+ import scriptSectionOrder from "./script-section-order.mjs";
13
+ import scriptDefineObject from "./script-define-object.mjs";
14
+
15
+ import storeRequireName from "./store-require-name.mjs";
16
+ import storeRequireModel from "./store-require-model.mjs";
17
+ import storeRequireView from "./store-require-view.mjs";
18
+ import storeRequireAction from "./store-require-action.mjs";
19
+ import storeSectionFunction from "./store-section-function.mjs";
20
+ import storeSectionMethod from "./store-section-method.mjs";
21
+ import storeSectionReturnShorthand from "./store-section-return-shorthand.mjs";
22
+ import storeConfigOrder from "./store-config-order.mjs";
23
+ import storeNoUnknownKey from "./store-no-unknown-key.mjs";
24
+ import storeSuffix from "./store-suffix.mjs";
25
+ import storeNameMatch from "./store-name-match.mjs";
26
+ import storeShapeSuffix from "./store-shape-suffix.mjs";
27
+ import storeModeEnum from "./store-mode-enum.mjs";
28
+
29
+ import composableNaming from "./composable-naming.mjs";
30
+
31
+ import radashPreferIs from "./radash-prefer-is.mjs";
32
+ import radashPreferCall from "./radash-prefer-call.mjs";
33
+ import radashPreferClone from "./radash-prefer-clone.mjs";
34
+ import radashPreferUnique from "./radash-prefer-unique.mjs";
35
+ import radashPreferSum from "./radash-prefer-sum.mjs";
36
+ import radashPreferSleep from "./radash-prefer-sleep.mjs";
37
+
38
+ import vueusePreferStorage from "./vueuse-prefer-storage.mjs";
39
+ import vueusePreferMemberCall from "./vueuse-prefer-member-call.mjs";
40
+ import vueusePreferTimer from "./vueuse-prefer-timer.mjs";
41
+ import vueusePreferObserver from "./vueuse-prefer-observer.mjs";
42
+ import vueusePreferClipboard from "./vueuse-prefer-clipboard.mjs";
43
+ import vueusePreferRoute from "./vueuse-prefer-route.mjs";
44
+
45
+ export const rules = {
46
+ // Template
47
+ "template-v-if": templateVIf,
48
+ "template-v-else": templateVElse,
49
+ "template-v-for": templateVFor,
50
+ "template-text": templateText,
51
+
52
+ // Script
53
+ "script-section-order": scriptSectionOrder,
54
+ "script-define-object": scriptDefineObject,
55
+
56
+ // Store — required keys
57
+ "store-require-name": storeRequireName,
58
+ "store-require-model": storeRequireModel,
59
+ "store-require-view": storeRequireView,
60
+ "store-require-action": storeRequireAction,
61
+
62
+ // Store — section shape
63
+ "store-section-function": storeSectionFunction,
64
+ "store-section-method": storeSectionMethod,
65
+ "store-section-return-shorthand": storeSectionReturnShorthand,
66
+
67
+ // Store — config integrity
68
+ "store-config-order": storeConfigOrder,
69
+ "store-no-unknown-key": storeNoUnknownKey,
70
+
71
+ // Store — naming
72
+ "store-suffix": storeSuffix,
73
+ "store-name-match": storeNameMatch,
74
+ "store-shape-suffix": storeShapeSuffix,
75
+
76
+ // Store — values
77
+ "store-mode-enum": storeModeEnum,
78
+
79
+ // Composable
80
+ "composable-naming": composableNaming,
81
+
82
+ // Radash
83
+ "radash-prefer-is": radashPreferIs,
84
+ "radash-prefer-call": radashPreferCall,
85
+ "radash-prefer-clone": radashPreferClone,
86
+ "radash-prefer-unique": radashPreferUnique,
87
+ "radash-prefer-sum": radashPreferSum,
88
+ "radash-prefer-sleep": radashPreferSleep,
89
+
90
+ // VueUse
91
+ "vueuse-prefer-storage": vueusePreferStorage,
92
+ "vueuse-prefer-member-call": vueusePreferMemberCall,
93
+ "vueuse-prefer-timer": vueusePreferTimer,
94
+ "vueuse-prefer-observer": vueusePreferObserver,
95
+ "vueuse-prefer-clipboard": vueusePreferClipboard,
96
+ "vueuse-prefer-route": vueusePreferRoute,
97
+ };
98
+
99
+ // Backfill each rule's docs metadata from its registered name so every rule
100
+ // exposes a valid `meta.docs.url` and `recommended` flag without repeating the
101
+ // name in the rule file itself.
102
+ for (const [name, rule] of Object.entries(rules)) {
103
+ rule.meta = rule.meta || {};
104
+ rule.meta.docs = { recommended: true, ...rule.meta.docs, url: docsUrl(name) };
105
+ }
@@ -0,0 +1,33 @@
1
+ // Prefer radash helpers over native global calls.
2
+
3
+ import { staticMemberName } from "../utils/ast.mjs";
4
+
5
+ // `<Global>.<method>(...)` → radash helper
6
+ const GLOBAL_CALLS = {
7
+ "Promise.all": "all",
8
+ "Array.isArray": "isArray",
9
+ };
10
+
11
+ export default {
12
+ meta: {
13
+ type: "suggestion",
14
+ docs: {
15
+ description: "prefer radash helpers over native global calls",
16
+ },
17
+ messages: {
18
+ preferCall: "Use radash '{{helper}}()' instead of '{{native}}()'.",
19
+ },
20
+ schema: [],
21
+ },
22
+ create(context) {
23
+ return {
24
+ CallExpression(node) {
25
+ const name = staticMemberName(node.callee);
26
+ const helper = name && GLOBAL_CALLS[name];
27
+ if (helper) {
28
+ context.report({ node: node.callee, messageId: "preferCall", data: { helper, native: name } });
29
+ }
30
+ },
31
+ };
32
+ },
33
+ };
@@ -0,0 +1,33 @@
1
+ // Prefer radash clone() over JSON.parse(JSON.stringify()).
2
+
3
+ import { staticMemberName } from "../utils/ast.mjs";
4
+
5
+ function isJsonStringifyCall(node) {
6
+ return node.type === "CallExpression" && staticMemberName(node.callee) === "JSON.stringify";
7
+ }
8
+
9
+ export default {
10
+ meta: {
11
+ type: "suggestion",
12
+ docs: {
13
+ description: "prefer radash clone() over JSON.parse(JSON.stringify())",
14
+ },
15
+ messages: {
16
+ preferClone: "Use radash 'clone()' instead of JSON.parse(JSON.stringify(...)).",
17
+ },
18
+ schema: [],
19
+ },
20
+ create(context) {
21
+ return {
22
+ CallExpression(node) {
23
+ if (
24
+ staticMemberName(node.callee) === "JSON.parse" &&
25
+ node.arguments[0] &&
26
+ isJsonStringifyCall(node.arguments[0])
27
+ ) {
28
+ context.report({ node, messageId: "preferClone" });
29
+ }
30
+ },
31
+ };
32
+ },
33
+ };