Skip to content

Commit d0d0b51

Browse files
authored
feat: #570 write eslint plugin to detect common mistakes (#688)
1 parent be3e5ff commit d0d0b51

File tree

23 files changed

+857
-86
lines changed

23 files changed

+857
-86
lines changed

.github/workflows/part-build.yml

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,36 @@ jobs:
108108
name: coalesce-vue
109109
path: src/coalesce-vue/coalesce-vue*.tgz
110110

111+
build-eslint-plugin-coalesce:
112+
runs-on: ubuntu-latest
113+
defaults:
114+
run:
115+
working-directory: src/eslint-plugin-coalesce
116+
117+
env:
118+
COALESCE_VERSION: ${{inputs.COALESCE_VERSION}}
119+
120+
steps:
121+
- uses: actions/checkout@v6
122+
123+
- run: npm ci
124+
working-directory: ${{ github.workspace }}
125+
126+
- name: npm run test
127+
run: npm run test
128+
129+
- name: update version
130+
run: sed -i "s/0.1.0-local/$COALESCE_VERSION/g" package.json
131+
132+
- run: npm run build
133+
- run: npm pack
134+
135+
- name: Upload Artifact
136+
uses: actions/upload-artifact@v7
137+
with:
138+
name: eslint-plugin-coalesce
139+
path: src/eslint-plugin-coalesce/*.tgz
140+
111141
build-coalesce-mcp:
112142
runs-on: ubuntu-latest
113143
defaults:
@@ -201,7 +231,13 @@ jobs:
201231
validate-template:
202232
runs-on: ubuntu-latest
203233
name: "test template: ${{matrix.testCase}}"
204-
needs: [build-dotnet, build-coalesce-vue, build-coalesce-vue-vuetify3]
234+
needs:
235+
[
236+
build-dotnet,
237+
build-coalesce-vue,
238+
build-coalesce-vue-vuetify3,
239+
build-eslint-plugin-coalesce,
240+
]
205241
strategy:
206242
matrix:
207243
testCase:
@@ -264,10 +300,12 @@ jobs:
264300
# Update package.json to use the newly built packages
265301
$coalescevuePath = "file:$((Get-ChildItem -Path '${{ github.workspace }}/artifacts/coalesce-vue/*.tgz').FullName)"
266302
$coalescevuetifyPath = "file:$((Get-ChildItem -Path '${{ github.workspace }}/artifacts/coalesce-vue-vuetify3/*.tgz').FullName)"
303+
$eslintPluginPath = "file:$((Get-ChildItem -Path '${{ github.workspace }}/artifacts/eslint-plugin-coalesce/*.tgz').FullName)"
267304
268305
$packageJson = Get-Content package.json | ConvertFrom-Json
269306
$packageJson.dependencies.'coalesce-vue' = $coalescevuePath
270307
$packageJson.dependencies.'coalesce-vue-vuetify3' = $coalescevuetifyPath
308+
$packageJson.devDependencies.'eslint-plugin-coalesce' = $eslintPluginPath
271309
$packageJson | ConvertTo-Json -Depth 10 | Set-Content package.json
272310
273311
cat package.json

.github/workflows/part-publish.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ jobs:
4545
if: ${{ !inputs.skipNpm }}
4646
run: for f in ./coalesce-vue-vuetify*/*.tgz; do npm publish $f --tag ${{ inputs.prereleaseSlug || 'latest' }} --access public; done
4747

48+
- name: npm publish eslint-plugin-coalesce
49+
if: ${{ !inputs.skipNpm }}
50+
run: npm publish ./eslint-plugin-coalesce/*.tgz --tag ${{ inputs.prereleaseSlug || 'latest' }} --access public
51+
4852
nuget:
4953
runs-on: ubuntu-latest
5054
permissions:

AGENTS.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,17 @@ dotnet build
3030
cd src/coalesce-vue && npm run build
3131
cd ../coalesce-vue-vuetify3 && npm run build
3232
cd ../coalesce-mcp && npm run build
33+
cd ../eslint-plugin-coalesce && npm run build
3334
```
3435

3536
### Run tests
3637

3738
```bash
3839
dotnet test
39-
cd src/coalesce-vue && npm test run
40-
cd ../coalesce-vue-vuetify3 && npm test run
41-
cd ../coalesce-mcp && npm test run
40+
cd src/coalesce-vue && npm run test
41+
cd ../coalesce-vue-vuetify3 && npm run test
42+
cd ../coalesce-mcp && npm run test
43+
cd ../eslint-plugin-coalesce && npm run test
4244
```
4345

4446
The .NET test projects use TUnit. To filter tests, pass `--treenode-filter` (run in the working directory of the specific test project):

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
- Added `app.UseAppVersionHeader()` middleware and `<CAppUpdateAlert>` component for detecting and notifying users when a new version of the application has been deployed.
33
- Assorted template improvements
44
- Added `[assembly: CoalesceMetadata<TAttribute>]`, an assembly-level attribute that causes any occurrences of the target attribute and its value to be generated into the TypeScript metadata.
5+
- Added `eslint-plugin-coalesce` package with rules to detect common mistakes: `no-load-before-auto-load`, `no-sort-in-computed`, and `no-static-router-import`.
56

67
# 6.3.0
78
- Added Vuetify 4 compatibility to coalesce-vue-vuetify3.

docs/.vitepress/config.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ export default defineConfig({
253253

254254
autoTitle("/topics/analyzers"),
255255
autoTitle("/topics/coalesce-json"),
256+
autoTitle("/topics/eslint-plugin"),
256257
autoTitle("/topics/immutability"),
257258
autoTitle("/topics/startup"),
258259
autoTitle("/topics/audit-logging"),

docs/topics/eslint-plugin.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# ESLint Plugin
2+
3+
Coalesce provides an ESLint plugin (`eslint-plugin-coalesce`) that detects common mistakes in Coalesce Vue applications. The plugin ships with a recommended configuration that enables all rules as warnings.
4+
5+
## Setup
6+
7+
The plugin is included in the [project template](/stacks/vue/getting-started.md) by default.
8+
9+
To add it to an existing project, install it:
10+
11+
```sh
12+
npm install --save-dev eslint-plugin-coalesce
13+
```
14+
15+
Then add the recommended config to your `eslint.config.mjs`:
16+
17+
```js
18+
import coalesce from "eslint-plugin-coalesce";
19+
20+
export default [
21+
// ... your other configs
22+
coalesce.configs.recommended,
23+
];
24+
```
25+
26+
## Rules
27+
28+
### `coalesce/no-load-before-auto-load`
29+
30+
Detects when `$load()` is called alongside `$useAutoLoad()` or `$startAutoLoad()` on the same object in the same scope. The auto-load methods support an `immediate` option that performs the initial load, making a separate `$load()` call redundant and potentially racy.
31+
32+
```ts
33+
// ❌ Bad
34+
const list = new PersonListViewModel();
35+
list.$load();
36+
list.$useAutoLoad();
37+
38+
// ✅ Good
39+
const list = new PersonListViewModel();
40+
list.$useAutoLoad({ immediate: true });
41+
```
42+
43+
### `coalesce/no-sort-in-computed`
44+
45+
Detects in-place `.sort()` calls inside `computed()` functions. Since `.sort()` mutates the array in-place, it can trigger reactivity updates that cause infinite loops.
46+
47+
The rule allows `.sort()` when chained after methods that produce new arrays (`.map()`, `.filter()`, `.slice()`, `.flatMap()`, `.concat()`, `.toReversed()`, `.toSpliced()`, `.flat()`), or when called on array literals like `[...arr].sort()`.
48+
49+
```ts
50+
// ❌ Bad
51+
const sorted = computed(() => items.value.sort());
52+
53+
// ✅ Good
54+
const sorted = computed(() => items.value.toSorted());
55+
const sorted = computed(() => [...items.value].sort());
56+
const sorted = computed(() => items.value.slice().sort());
57+
```
58+
59+
### `coalesce/no-static-router-import`
60+
61+
In `.vue` files, detects direct default imports of the router instance (e.g. `import router from '@/router'`), which almost always creates circular dependencies. Use `useRouter()` from `vue-router` instead.
62+
63+
```ts
64+
// ❌ Bad (in a .vue file)
65+
import router from "@/router";
66+
67+
// ✅ Good
68+
import { useRouter } from "vue-router";
69+
const router = useRouter();
70+
```
71+
72+
## Configuration
73+
74+
All rules are enabled as warnings in the recommended config. To customize severity:
75+
76+
```js
77+
import coalesce from "eslint-plugin-coalesce";
78+
79+
export default [
80+
coalesce.configs.recommended,
81+
{
82+
rules: {
83+
"coalesce/no-sort-in-computed": "error",
84+
"coalesce/no-static-router-import": "off",
85+
},
86+
},
87+
];
88+
```

0 commit comments

Comments
 (0)