@evertondgn/solid-tooling 0.0.0-stage → 0.2.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.
- package/LICENSE +21 -0
- package/README.md +257 -2
- package/dist/architecture-plugin.d.mts +2 -0
- package/dist/architecture-plugin.mjs +82 -0
- package/dist/backend-plugin.d.mts +2 -0
- package/dist/backend-plugin.mjs +37 -0
- package/dist/constants-Jqvtbvxu.mjs +7 -0
- package/dist/css-filename.d.mts +2 -0
- package/dist/css-filename.mjs +40 -0
- package/dist/fmt.d.mts +2 -0
- package/dist/fmt.mjs +34 -0
- package/dist/import-specifier-C7HWhMXB.mjs +25 -0
- package/dist/lint.d.mts +2 -0
- package/dist/lint.mjs +983 -0
- package/dist/project-plugin.d.mts +2 -0
- package/dist/project-plugin.mjs +209 -0
- package/dist/solid-plugin.d.mts +2 -0
- package/dist/solid-plugin.mjs +1 -0
- package/package.json +56 -4
- package/tsconfig.base.json +26 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Éverton Toffanetto
|
|
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
CHANGED
|
@@ -1,3 +1,258 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @evertondgn/solid-tooling
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Lint preset, formatter config and TypeScript base for SolidJS 2 applications
|
|
4
|
+
built with [Vite+](https://viteplus.dev/guide/).
|
|
5
|
+
|
|
6
|
+
The package contains the lint, format and TypeScript setup of
|
|
7
|
+
[boilerplate_solidjs](https://github.com/everton-dgn/boilerplate_solidjs), a
|
|
8
|
+
SolidJS application template. Applications created from it install the package
|
|
9
|
+
instead of copying the files, so a rule change reaches them through a version
|
|
10
|
+
update.
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
The application installs three packages next to this one:
|
|
15
|
+
|
|
16
|
+
| Package | Why |
|
|
17
|
+
| ---------------------- | ----------------------------------------------------------- |
|
|
18
|
+
| `vite-plus` | Runs lint and format, and provides the Oxlint plugin API |
|
|
19
|
+
| `@inlang/paraglide-js` | The preset registers its lint plugin, resolved from the app |
|
|
20
|
+
| `typescript` | The preset turns on type-aware linting and type checking |
|
|
21
|
+
|
|
22
|
+
`vite-plus` and `@inlang/paraglide-js` are peer dependencies, pinned to the
|
|
23
|
+
exact versions the package is tested with. Install the versions listed in its
|
|
24
|
+
`peerDependencies`. `typescript` is not a peer dependency: the package is tested
|
|
25
|
+
with the version in its `devDependencies`.
|
|
26
|
+
|
|
27
|
+
`eslint-plugin-solid` comes with the package, so the application does not
|
|
28
|
+
declare it.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
`vp` is the Vite+ CLI. An application built with Vite+ already has `vite-plus`
|
|
33
|
+
and `typescript`, so it only adds this package:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
vp add -D @evertondgn/solid-tooling
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
An application without `@inlang/paraglide-js` adds it too, in the version the
|
|
40
|
+
package asks for:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm view @evertondgn/solid-tooling peerDependencies
|
|
44
|
+
vp add -D @inlang/paraglide-js@<version>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
// vite.config.ts
|
|
51
|
+
import { fmt } from '@evertondgn/solid-tooling/fmt'
|
|
52
|
+
import { lint } from '@evertondgn/solid-tooling/lint'
|
|
53
|
+
import { defineConfig } from 'vite-plus'
|
|
54
|
+
|
|
55
|
+
export default defineConfig({ fmt, lint })
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
// tsconfig.json
|
|
60
|
+
{
|
|
61
|
+
"extends": "@evertondgn/solid-tooling/tsconfig.base.json",
|
|
62
|
+
"compilerOptions": {
|
|
63
|
+
"paths": { "@/*": ["./src/*"] }
|
|
64
|
+
},
|
|
65
|
+
"include": ["src", "vite.config.ts"]
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`vp check`, `vp lint` and `vp fmt` read both configs from `vite.config.ts`.
|
|
70
|
+
|
|
71
|
+
## Project layout the preset expects
|
|
72
|
+
|
|
73
|
+
The rules match file paths relative to the folder where lint runs, so run it
|
|
74
|
+
from the application root. They know these locations:
|
|
75
|
+
|
|
76
|
+
| Path | Meaning |
|
|
77
|
+
| --------------------------------------------- | -------------------------------------------------------- |
|
|
78
|
+
| `src/` | Application code. The `@/` alias points here |
|
|
79
|
+
| `src/routes/` | Pages. Each page may have a `seo.ts` module |
|
|
80
|
+
| `src/components/{atoms,molecules,organisms}/` | UI layers |
|
|
81
|
+
| `src/primitives/` | Shared primitives |
|
|
82
|
+
| `src/infra/server/` | Backend adapters and server error handling |
|
|
83
|
+
| `src/theme/` | Global CSS |
|
|
84
|
+
| `src/tests/pages/<Page>/` | End-to-end tests, one folder per page |
|
|
85
|
+
| `paraglide/` | Generated i18n modules. The `@paraglide/` alias |
|
|
86
|
+
| `tooling/` | Project scripts and their tests, written for `node:test` |
|
|
87
|
+
|
|
88
|
+
An application with another layout can still use the preset, but it has to
|
|
89
|
+
override the rules that do not fit. See
|
|
90
|
+
[Changing the preset](#changing-the-preset).
|
|
91
|
+
|
|
92
|
+
## What the lint preset turns on
|
|
93
|
+
|
|
94
|
+
- Oxlint plugins: `import`, `jsx-a11y`, `node`, `oxc`, `promise`, `typescript`,
|
|
95
|
+
`unicorn` and `vitest`.
|
|
96
|
+
- JavaScript plugins: `solid`, `paraglide`, `vite-plus` and the three plugins of
|
|
97
|
+
this package (`project`, `backend`, `architecture`).
|
|
98
|
+
- Type-aware rules and type checking (`typeAware`, `typeCheck`).
|
|
99
|
+
- Warnings fail the run (`denyWarnings`), and an unused disable directive is an
|
|
100
|
+
error.
|
|
101
|
+
|
|
102
|
+
Every rule is listed by name in the preset. No Oxlint category is enabled, so an
|
|
103
|
+
Oxlint update does not add rules on its own.
|
|
104
|
+
|
|
105
|
+
### Rules of this package
|
|
106
|
+
|
|
107
|
+
| Rule | What it enforces |
|
|
108
|
+
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
109
|
+
| `project/css-modules-import` | A CSS Module is imported as the default binding `S` |
|
|
110
|
+
| `project/css-filename` | A CSS file imported from `src/` is named `styles.module.css`, or `camelCase.css` inside `src/theme/` |
|
|
111
|
+
| `project/test-filename` | A test file sits in `__tests__` next to its module and is named after it, plus the environment: `Button/__tests__/Button.dom.test.tsx` |
|
|
112
|
+
| `project/no-export-list` | Exports sit on the declaration. `export { a, b }` is only allowed with `from` |
|
|
113
|
+
| `project/seo-module` | A route `seo.ts` exports only `seo`. It has no relative value import, no value import from `src/routes/`, no computed `import()` and no `import.meta.glob` |
|
|
114
|
+
| `backend/static-solid-imports` | No `import()` of `@solidjs/web` or its subpaths |
|
|
115
|
+
| `backend/static-render-integrity-imports` | No `import()` of the `renderIntegrity` module |
|
|
116
|
+
| `architecture/layer-imports` | Lower layers do not import upper ones, and production code does not import tests or `tooling/` |
|
|
117
|
+
|
|
118
|
+
These rules read import specifiers as text. They resolve relative paths and the
|
|
119
|
+
`@/`, `@paraglide/` and `/src/` prefixes. An `import()` with a computed path and
|
|
120
|
+
any other alias are not checked, except in `seo.ts`, where a computed `import()`
|
|
121
|
+
is an error.
|
|
122
|
+
|
|
123
|
+
`architecture/layer-imports` checks these directions:
|
|
124
|
+
|
|
125
|
+
- The base layer (`src/@types`, `constants`, `helpers`, `data`, `infra`,
|
|
126
|
+
`theme`, `i18n` and `paraglide/`) does not import primitives, components,
|
|
127
|
+
routes or entry files.
|
|
128
|
+
- `src/primitives` does not import components, routes or entry files.
|
|
129
|
+
- Atoms do not import molecules or organisms, and molecules do not import
|
|
130
|
+
organisms.
|
|
131
|
+
- No production file imports a test or `tooling/`.
|
|
132
|
+
|
|
133
|
+
The entry files are `App`, `Document`, `entry-server`, `entry-client`, `router`
|
|
134
|
+
and `middleware`, at the root of `src/`.
|
|
135
|
+
|
|
136
|
+
`project/test-filename` accepts these names:
|
|
137
|
+
|
|
138
|
+
- `Name.<environment>.test.ts(x)` in `src/`, where the environment is `node`,
|
|
139
|
+
`dom`, `browser` or `e2e`.
|
|
140
|
+
- `Name.test-d.ts(x)` in `src/`, for type tests.
|
|
141
|
+
- `Name.test.ts` in `tooling/`.
|
|
142
|
+
|
|
143
|
+
`Name` is the folder that holds `__tests__`. A qualifier may follow it:
|
|
144
|
+
`Button.focus.dom.test.tsx`. End-to-end tests are the exception: they live in
|
|
145
|
+
`src/tests/pages/<Page>/` and are named after the page.
|
|
146
|
+
|
|
147
|
+
### Backend restrictions
|
|
148
|
+
|
|
149
|
+
Inside `src/`, the preset also restricts what code can reach:
|
|
150
|
+
|
|
151
|
+
- `console` is not allowed. `src/infra/server/logServerFailure/index.ts` is the
|
|
152
|
+
only module that may call `console.error`.
|
|
153
|
+
- `fetch`, `XMLHttpRequest`, `WebSocket` and `EventSource` are not allowed.
|
|
154
|
+
`src/infra/server/requestJson/index.ts` is the only module that may call
|
|
155
|
+
`fetch`.
|
|
156
|
+
- The Node transports (`http`, `https`, `http2`, `net`, `tls`, `undici`) cannot
|
|
157
|
+
be imported.
|
|
158
|
+
- `markSafeError` and `SAFE_ERROR` can only be imported in
|
|
159
|
+
`src/infra/server/publicErrors/index.ts`.
|
|
160
|
+
- `configureServerErrors` from `@solidjs/web`, the
|
|
161
|
+
`@solidjs/web/server-functions` subpath and `observeRenderFailures` from the
|
|
162
|
+
application's `renderIntegrity` module can only be imported in
|
|
163
|
+
`src/infra/server/configureServerErrors/index.ts`.
|
|
164
|
+
- A relative import goes up at most three levels. Use `@/` beyond that.
|
|
165
|
+
|
|
166
|
+
Test files and `.d.ts` files are exempt from all of these except the relative
|
|
167
|
+
import limit.
|
|
168
|
+
|
|
169
|
+
## Changing the preset
|
|
170
|
+
|
|
171
|
+
`lint` and `fmt` are plain objects. Spread them and replace what the application
|
|
172
|
+
needs:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
// vite.config.ts
|
|
176
|
+
import { fmt } from '@evertondgn/solid-tooling/fmt'
|
|
177
|
+
import { lint } from '@evertondgn/solid-tooling/lint'
|
|
178
|
+
import { defineConfig } from 'vite-plus'
|
|
179
|
+
|
|
180
|
+
export default defineConfig({
|
|
181
|
+
fmt,
|
|
182
|
+
lint: {
|
|
183
|
+
...lint,
|
|
184
|
+
ignorePatterns: [...(lint.ignorePatterns ?? []), 'generated/**'],
|
|
185
|
+
rules: { ...lint.rules, 'eslint/max-params': 'off' }
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Do not replace `lint.jsPlugins`. It registers the JavaScript plugins listed
|
|
191
|
+
above, and a rule whose plugin is missing from it fails to resolve.
|
|
192
|
+
|
|
193
|
+
To turn a rule off for some files only, add an entry to `lint.overrides` and
|
|
194
|
+
keep the existing ones:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
overrides: [
|
|
198
|
+
...(lint.overrides ?? []),
|
|
199
|
+
{
|
|
200
|
+
files: ['src/tests/pages/Checkout/**'],
|
|
201
|
+
rules: { 'project/test-filename': 'off' }
|
|
202
|
+
}
|
|
203
|
+
]
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Entry points
|
|
207
|
+
|
|
208
|
+
| Entry | Content |
|
|
209
|
+
| ----------------------- | ------------------------------------------------------------------------- |
|
|
210
|
+
| `./lint` | `lint`: the lint preset, with the plugins below registered |
|
|
211
|
+
| `./fmt` | `fmt`: the formatter config |
|
|
212
|
+
| `./tsconfig.base.json` | Compiler options, without application paths |
|
|
213
|
+
| `./project-plugin` | The `project` rules |
|
|
214
|
+
| `./backend-plugin` | The `backend` rules |
|
|
215
|
+
| `./architecture-plugin` | The `architecture` rule |
|
|
216
|
+
| `./solid-plugin` | Re-export of `eslint-plugin-solid` |
|
|
217
|
+
| `./css-filename` | `cssFilenameError(path)`, the CSS naming check, for a script outside lint |
|
|
218
|
+
|
|
219
|
+
An application only imports `./lint`, `./fmt` and `./tsconfig.base.json`. The
|
|
220
|
+
plugin entries exist because Oxlint loads plugins by specifier, from the
|
|
221
|
+
application.
|
|
222
|
+
|
|
223
|
+
## Formatter
|
|
224
|
+
|
|
225
|
+
`fmt` sets 80 columns, single quotes, no semicolons, no trailing commas, wrapped
|
|
226
|
+
prose in Markdown and sorted imports. It ignores `public/`, `src/assets/`,
|
|
227
|
+
generated route types, SVG and lock files.
|
|
228
|
+
|
|
229
|
+
## TypeScript base
|
|
230
|
+
|
|
231
|
+
`tsconfig.base.json` targets ES2024 with bundler resolution, keeps JSX for the
|
|
232
|
+
Solid compiler (`jsxImportSource: @solidjs/web`), emits nothing and turns on the
|
|
233
|
+
unused code and unchecked index checks. It has no `paths`, `include` or `types`:
|
|
234
|
+
those depend on the application and stay in its `tsconfig.json`.
|
|
235
|
+
|
|
236
|
+
## Known limits
|
|
237
|
+
|
|
238
|
+
- The package ships compiled JavaScript. Node refuses to run TypeScript from
|
|
239
|
+
`node_modules`, and Vite+ loads `vite.config.ts` in Node.
|
|
240
|
+
- `project/test-filename` has no options. Test folders without a module, such as
|
|
241
|
+
the page tests in `src/tests/pages/`, are listed inside the package with the
|
|
242
|
+
names used in boilerplate_solidjs. For any other folder of that kind, turn the
|
|
243
|
+
rule off in `lint.overrides`, as shown in
|
|
244
|
+
[Changing the preset](#changing-the-preset).
|
|
245
|
+
- The Paraglide plugin is always registered, so `@inlang/paraglide-js` has to be
|
|
246
|
+
installed even in an application without translations.
|
|
247
|
+
- Some rule messages point to `docs/architecture.md` and `AGENTS.md`. Those
|
|
248
|
+
files belong to the application created from boilerplate_solidjs.
|
|
249
|
+
|
|
250
|
+
## Versions
|
|
251
|
+
|
|
252
|
+
The package follows semantic versioning. Below 1.0.0, a breaking change raises
|
|
253
|
+
the minor version. Each release is listed in the
|
|
254
|
+
[changelog](https://github.com/everton-dgn/solidjs_base/blob/main/packages/solid-tooling/CHANGELOG.md).
|
|
255
|
+
|
|
256
|
+
## License
|
|
257
|
+
|
|
258
|
+
MIT
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { n as resolveProjectPath, t as readStaticSpecifier } from "./import-specifier-C7HWhMXB.mjs";
|
|
2
|
+
import { definePlugin, defineRule } from "vite-plus/lint/plugins";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
// src/architecturePolicy/index.ts
|
|
5
|
+
const UI = [
|
|
6
|
+
"atoms",
|
|
7
|
+
"molecules",
|
|
8
|
+
"organisms",
|
|
9
|
+
"components"
|
|
10
|
+
];
|
|
11
|
+
const FORBIDDEN = {
|
|
12
|
+
base: [
|
|
13
|
+
"primitives",
|
|
14
|
+
...UI,
|
|
15
|
+
"routes",
|
|
16
|
+
"entry"
|
|
17
|
+
],
|
|
18
|
+
primitives: [
|
|
19
|
+
...UI,
|
|
20
|
+
"routes",
|
|
21
|
+
"entry"
|
|
22
|
+
],
|
|
23
|
+
atoms: ["molecules", "organisms"],
|
|
24
|
+
molecules: ["organisms"]
|
|
25
|
+
};
|
|
26
|
+
function layer(file) {
|
|
27
|
+
if (file === "tooling" || file.startsWith("tooling/")) return "tooling";
|
|
28
|
+
if (file.startsWith("paraglide/")) return "base";
|
|
29
|
+
if (!file.startsWith("src/")) return void 0;
|
|
30
|
+
if (/^src\/tests(?:\/|$)|\/__tests__(?:\/|$)|\.test(?:\.[cm]?[jt]sx?)?$/u.test(file)) return "test";
|
|
31
|
+
if (/^src\/(?:@types|constants|helpers|data|infra|theme|i18n)(?:\/|$)/u.test(file)) return "base";
|
|
32
|
+
if (/^src\/primitives(?:\/|$)/u.test(file)) return "primitives";
|
|
33
|
+
const level = /^src\/components\/(?<level>atoms|molecules|organisms)(?:\/|\.[cm]?[jt]sx?$|$)/u.exec(file)?.groups?.level;
|
|
34
|
+
if (level === "atoms" || level === "molecules" || level === "organisms") return level;
|
|
35
|
+
if (/^src\/components(?:\/|$)/u.test(file)) return "components";
|
|
36
|
+
if (/^src\/routes(?:\/|$)/u.test(file)) return "routes";
|
|
37
|
+
if (/^src\/(?:App|Document|entry-server|entry-client|router|middleware)(?:\.[cm]?[jt]sx?$|\/|$)/u.test(file)) return "entry";
|
|
38
|
+
return "source";
|
|
39
|
+
}
|
|
40
|
+
const layerImports = defineRule({
|
|
41
|
+
meta: {
|
|
42
|
+
type: "problem",
|
|
43
|
+
schema: [],
|
|
44
|
+
messages: { forbidden: "Layer {{from}} must not depend on {{to}} ({{target}}). See the layer rules in docs/architecture.md." }
|
|
45
|
+
},
|
|
46
|
+
create(context) {
|
|
47
|
+
const from = layer(path.relative(context.cwd, context.filename).split(path.sep).join("/"));
|
|
48
|
+
if (!from || from === "test" || from === "tooling") return {};
|
|
49
|
+
const forbidden = FORBIDDEN[from] ?? [];
|
|
50
|
+
function check(node) {
|
|
51
|
+
const specifier = readStaticSpecifier(node);
|
|
52
|
+
if (specifier === void 0) return;
|
|
53
|
+
const target = resolveProjectPath({
|
|
54
|
+
specifier,
|
|
55
|
+
filename: context.filename,
|
|
56
|
+
root: context.cwd
|
|
57
|
+
});
|
|
58
|
+
const to = layer(target);
|
|
59
|
+
if (to && (to === "test" || to === "tooling" || forbidden.includes(to))) context.report({
|
|
60
|
+
node,
|
|
61
|
+
messageId: "forbidden",
|
|
62
|
+
data: {
|
|
63
|
+
from,
|
|
64
|
+
to,
|
|
65
|
+
target
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
ImportDeclaration: check,
|
|
71
|
+
ExportNamedDeclaration: check,
|
|
72
|
+
ExportAllDeclaration: check,
|
|
73
|
+
ImportExpression: check,
|
|
74
|
+
TSImportType: check
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
const architecturePolicy = definePlugin({
|
|
79
|
+
meta: { name: "architecture" },
|
|
80
|
+
rules: { "layer-imports": layerImports }
|
|
81
|
+
});
|
|
82
|
+
export { architecturePolicy as default };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { t as readStaticSpecifier } from "./import-specifier-C7HWhMXB.mjs";
|
|
2
|
+
import { n as RENDER_INTEGRITY_MODULE } from "./constants-Jqvtbvxu.mjs";
|
|
3
|
+
import { definePlugin, defineRule } from "vite-plus/lint/plugins";
|
|
4
|
+
// src/backendPolicy/plugin.ts
|
|
5
|
+
const RENDER_INTEGRITY = new RegExp(RENDER_INTEGRITY_MODULE, "u");
|
|
6
|
+
function staticImportRule({ matches, message }) {
|
|
7
|
+
return defineRule({
|
|
8
|
+
meta: {
|
|
9
|
+
type: "problem",
|
|
10
|
+
schema: [],
|
|
11
|
+
messages: { staticImport: message }
|
|
12
|
+
},
|
|
13
|
+
create(context) {
|
|
14
|
+
return { ImportExpression(node) {
|
|
15
|
+
const specifier = readStaticSpecifier(node);
|
|
16
|
+
if (specifier !== void 0 && matches(specifier)) context.report({
|
|
17
|
+
node,
|
|
18
|
+
messageId: "staticImport"
|
|
19
|
+
});
|
|
20
|
+
} };
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const backendPlugin = definePlugin({
|
|
25
|
+
meta: { name: "backend" },
|
|
26
|
+
rules: {
|
|
27
|
+
"static-solid-imports": staticImportRule({
|
|
28
|
+
matches: (specifier) => specifier === "@solidjs/web" || specifier.startsWith("@solidjs/web/"),
|
|
29
|
+
message: "Use a static import for @solidjs/web and its subpaths: the import restrictions on markSafeError and configureServerErrors cannot check import()."
|
|
30
|
+
}),
|
|
31
|
+
"static-render-integrity-imports": staticImportRule({
|
|
32
|
+
matches: (specifier) => RENDER_INTEGRITY.test(specifier),
|
|
33
|
+
message: "Use a static import for renderIntegrity: the import restriction on observeRenderFailures cannot check import()."
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
export { backendPlugin as default };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// src/backendPolicy/constants.ts
|
|
2
|
+
const RELATIVE_IMPORT_RESTRICTION = {
|
|
3
|
+
regex: String.raw`^(\.\./){4,}`,
|
|
4
|
+
message: "Relative imports may go up at most three levels. Use the @/ alias for more distant paths."
|
|
5
|
+
};
|
|
6
|
+
const RENDER_INTEGRITY_MODULE = String.raw`(?:^|/)renderIntegrity(?:/index(?:\.ts)?)?$`;
|
|
7
|
+
export { RENDER_INTEGRITY_MODULE as n, RELATIVE_IMPORT_RESTRICTION as t };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { n as resolveProjectPath, t as readStaticSpecifier } from "./import-specifier-C7HWhMXB.mjs";
|
|
2
|
+
import { defineRule } from "vite-plus/lint/plugins";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
// src/css/css-filename.ts
|
|
5
|
+
function cssFilenameError(file) {
|
|
6
|
+
const normalized = file.split("\\").join("/");
|
|
7
|
+
const basename = path.posix.basename(normalized);
|
|
8
|
+
if (normalized.startsWith("src/theme/")) return /^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$/u.test(basename.slice(0, -4)) ? void 0 : "Use camelCaseName.css inside src/theme/.";
|
|
9
|
+
return basename === "styles.module.css" ? void 0 : "Use styles.module.css outside src/theme/, including in routes.";
|
|
10
|
+
}
|
|
11
|
+
const cssFilename = defineRule({
|
|
12
|
+
meta: {
|
|
13
|
+
type: "problem",
|
|
14
|
+
schema: []
|
|
15
|
+
},
|
|
16
|
+
create(context) {
|
|
17
|
+
function check(node) {
|
|
18
|
+
const specifier = readStaticSpecifier(node);
|
|
19
|
+
if (specifier === void 0) return;
|
|
20
|
+
const relative = resolveProjectPath({
|
|
21
|
+
specifier,
|
|
22
|
+
filename: context.filename,
|
|
23
|
+
root: context.cwd
|
|
24
|
+
});
|
|
25
|
+
if (!relative.startsWith("src/") || !relative.endsWith(".css")) return;
|
|
26
|
+
const message = cssFilenameError(relative);
|
|
27
|
+
if (message) context.report({
|
|
28
|
+
node,
|
|
29
|
+
message
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
ImportDeclaration: check,
|
|
34
|
+
ExportNamedDeclaration: check,
|
|
35
|
+
ExportAllDeclaration: check,
|
|
36
|
+
ImportExpression: check
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
export { cssFilename, cssFilenameError };
|
package/dist/fmt.d.mts
ADDED
package/dist/fmt.mjs
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// src/fmt.ts
|
|
2
|
+
const fmt = {
|
|
3
|
+
printWidth: 80,
|
|
4
|
+
singleQuote: true,
|
|
5
|
+
semi: false,
|
|
6
|
+
trailingComma: "none",
|
|
7
|
+
arrowParens: "avoid",
|
|
8
|
+
proseWrap: "always",
|
|
9
|
+
sortPackageJson: false,
|
|
10
|
+
embeddedLanguageFormatting: "off",
|
|
11
|
+
sortImports: { groups: [
|
|
12
|
+
"builtin",
|
|
13
|
+
"external",
|
|
14
|
+
["internal", "subpath"],
|
|
15
|
+
[
|
|
16
|
+
"parent",
|
|
17
|
+
"sibling",
|
|
18
|
+
"index"
|
|
19
|
+
],
|
|
20
|
+
"style",
|
|
21
|
+
"unknown"
|
|
22
|
+
] },
|
|
23
|
+
ignorePatterns: [
|
|
24
|
+
"public/**",
|
|
25
|
+
"src/assets/**",
|
|
26
|
+
"src/@types/routes.d.ts",
|
|
27
|
+
"**/*.svg",
|
|
28
|
+
"*.lock",
|
|
29
|
+
"AGENTS.md",
|
|
30
|
+
".agents/**",
|
|
31
|
+
".claude/**"
|
|
32
|
+
]
|
|
33
|
+
};
|
|
34
|
+
export { fmt };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
// src/lint/import-specifier.ts
|
|
3
|
+
function resolveAbsolute({ specifier, filename, root }) {
|
|
4
|
+
if (specifier.startsWith("@paraglide/")) return path.resolve(root, "paraglide", specifier.slice(11));
|
|
5
|
+
if (specifier.startsWith("@/")) return path.resolve(root, "src", specifier.slice(2));
|
|
6
|
+
if (specifier.startsWith("/src/")) return path.resolve(root, specifier.slice(1));
|
|
7
|
+
if (specifier.startsWith(".")) return path.resolve(path.dirname(filename), specifier);
|
|
8
|
+
}
|
|
9
|
+
function readStaticSpecifier({ source }) {
|
|
10
|
+
if (source?.type === "Literal") return typeof source.value === "string" ? source.value : void 0;
|
|
11
|
+
if (source?.type !== "TemplateLiteral" || source.expressions.length > 0) return;
|
|
12
|
+
const cooked = source.quasis[0]?.value.cooked;
|
|
13
|
+
return typeof cooked === "string" ? cooked : void 0;
|
|
14
|
+
}
|
|
15
|
+
function resolveProjectPath({ specifier, filename, root }) {
|
|
16
|
+
const [clean = ""] = specifier.replaceAll("\\", "/").split(/[?#]/u);
|
|
17
|
+
const absolute = resolveAbsolute({
|
|
18
|
+
specifier: clean,
|
|
19
|
+
filename,
|
|
20
|
+
root
|
|
21
|
+
});
|
|
22
|
+
if (absolute === void 0) return "";
|
|
23
|
+
return path.relative(root, absolute).split(path.sep).join("/");
|
|
24
|
+
}
|
|
25
|
+
export { resolveProjectPath as n, readStaticSpecifier as t };
|
package/dist/lint.d.mts
ADDED