@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.
@@ -0,0 +1,2 @@
1
+ declare const plugin: import("vite-plus/lint/plugins").Plugin;
2
+ export { plugin as default };
@@ -0,0 +1,209 @@
1
+ import { cssFilename } from "./css-filename.mjs";
2
+ import { n as resolveProjectPath, t as readStaticSpecifier } from "./import-specifier-C7HWhMXB.mjs";
3
+ import { definePlugin, defineRule } from "vite-plus/lint/plugins";
4
+ import path from "node:path";
5
+ // src/noExportList/index.ts
6
+ const noExportList = defineRule({
7
+ meta: {
8
+ type: "suggestion",
9
+ schema: [],
10
+ messages: { inlineExport: "Export on the declaration itself (export function, export const, export type) instead of using export { ... }. An export list is only allowed in a re-export with from." }
11
+ },
12
+ create(context) {
13
+ return { ExportNamedDeclaration(node) {
14
+ if (node.declaration || node.source) return;
15
+ context.report({
16
+ node,
17
+ messageId: "inlineExport"
18
+ });
19
+ } };
20
+ }
21
+ });
22
+ // src/seoModule/index.ts
23
+ const SEO_MODULE = /^src\/routes\/(?:.+\/)?seo\.ts$/u;
24
+ const LOCAL_SPECIFIER = /^\.{1,2}(?:\/|$)/u;
25
+ const SEO_EXPORT = "seo";
26
+ const ROUTES_DIR = "src/routes/";
27
+ function isImportMetaGlob({ computed, object, property }) {
28
+ return !computed && object.type === "MetaProperty" && object.meta.name === "import" && object.property.name === "meta" && property.type === "Identifier" && property.name === "glob";
29
+ }
30
+ const seoModule = defineRule({
31
+ meta: {
32
+ type: "problem",
33
+ schema: [],
34
+ messages: {
35
+ onlySeo: "seo.ts may export only seo. The route manifest loads the route export of every page in the initial bundle, and seo.ts goes with it, so keep the page body in index.tsx (see the page modules section of AGENTS.md).",
36
+ localImport: "seo.ts must not import values from relative paths or from src/routes/: whatever it imports goes into the initial bundle. Use import type, or keep the code in index.tsx (see the page modules section of AGENTS.md).",
37
+ computedImport: "seo.ts must not use import() with a computed path: the rule cannot tell whether the target is local. Keep the page body in index.tsx (see the page modules section of AGENTS.md).",
38
+ globImport: "seo.ts must not use import.meta.glob: Vite turns it into imports of every module the pattern matches. Keep the page body in index.tsx (see the page modules section of AGENTS.md)."
39
+ }
40
+ },
41
+ create(context) {
42
+ const relative = path.relative(context.cwd, context.filename).split(path.sep).join("/");
43
+ if (!SEO_MODULE.test(relative)) return {};
44
+ const reportLocalImport = (node) => {
45
+ const specifier = readStaticSpecifier(node);
46
+ if (specifier === void 0) {
47
+ context.report({
48
+ node,
49
+ messageId: "computedImport"
50
+ });
51
+ return;
52
+ }
53
+ const target = resolveProjectPath({
54
+ specifier,
55
+ filename: context.filename,
56
+ root: context.cwd
57
+ });
58
+ if (LOCAL_SPECIFIER.test(specifier) || target.startsWith(ROUTES_DIR)) context.report({
59
+ node,
60
+ messageId: "localImport"
61
+ });
62
+ };
63
+ return {
64
+ ExportNamedDeclaration(node) {
65
+ const { declaration } = node;
66
+ if (declaration?.type === "VariableDeclaration") {
67
+ if (declaration.declarations.every(({ id }) => id.type === "Identifier" && id.name === SEO_EXPORT)) return;
68
+ }
69
+ context.report({
70
+ node,
71
+ messageId: "onlySeo"
72
+ });
73
+ },
74
+ ExportDefaultDeclaration(node) {
75
+ context.report({
76
+ node,
77
+ messageId: "onlySeo"
78
+ });
79
+ },
80
+ ExportAllDeclaration(node) {
81
+ context.report({
82
+ node,
83
+ messageId: "onlySeo"
84
+ });
85
+ },
86
+ ImportDeclaration(node) {
87
+ if (node.importKind !== "type") reportLocalImport(node);
88
+ },
89
+ ImportExpression(node) {
90
+ reportLocalImport(node);
91
+ },
92
+ MemberExpression(node) {
93
+ if (isImportMetaGlob(node)) context.report({
94
+ node,
95
+ messageId: "globImport"
96
+ });
97
+ }
98
+ };
99
+ }
100
+ });
101
+ // src/test-filename.ts
102
+ const subjects = {
103
+ "tooling/__tests__": [
104
+ "lint",
105
+ "patched-dependencies",
106
+ "playwright",
107
+ "test-filename"
108
+ ],
109
+ "src/__tests__": ["App", "PageFileSystemRouter"],
110
+ "src/constants/__tests__": ["cache"],
111
+ "src/tests/pages/Home": ["Home"],
112
+ "src/tests/pages/Hydration": ["Hydration"],
113
+ "src/tests/pages/NotFound": ["NotFound"],
114
+ "src/tests/pages/BackendError": ["BackendError"],
115
+ "src/tests/pages/FragmentCss": ["FragmentCss"],
116
+ "src/tests/pages/LateRedirect": ["LateRedirect"],
117
+ "src/tests/pages/OutsideError": ["OutsideError"],
118
+ "src/tests/pages/OutdatedPage": ["OutdatedPage"],
119
+ "src/tests/pages/SeoIndexes": ["SeoIndexes"],
120
+ "src/tests/pages/AgentReadiness": ["AgentReadiness"],
121
+ "src/tests/pages/BuildOutput": ["BuildOutput"],
122
+ "src/tests/pages/I18n": ["I18n"],
123
+ "src/tests/pages/Markdown": ["Markdown"],
124
+ "src/tests/pages/PublicationLoad": ["PublicationLoad"],
125
+ "src/tests/pages/UiKit": ["UiKit"],
126
+ "src/tests/pages/StructuredData": ["StructuredData"],
127
+ "tooling/css/__tests__": ["cssPolicy"],
128
+ "tooling/release/__tests__": ["automatic-release", "versioning"]
129
+ };
130
+ const DOTTED_SUBJECT = "subject";
131
+ function expectedSubjects(directory) {
132
+ const declared = subjects[directory];
133
+ if (declared || path.posix.basename(directory) !== "__tests__") return declared;
134
+ return [path.posix.basename(path.posix.dirname(directory))];
135
+ }
136
+ function dottedSubject({ expected, basename }) {
137
+ return expected?.find((name) => name.includes(".") && basename.startsWith(`${name}.`));
138
+ }
139
+ function isTestSupportModule(file) {
140
+ return !file.startsWith("src/tests/pages/") && /\/__tests__\/(?:fixtures|types|constants)\.ts$/u.test(file);
141
+ }
142
+ function testFilenameError(file) {
143
+ const normalized = path.posix.normalize(file.replaceAll("\\", "/"));
144
+ if (!/^(?:src|tooling)\//u.test(normalized)) return void 0;
145
+ const basename = path.posix.basename(normalized);
146
+ const directory = path.posix.dirname(normalized);
147
+ const inPages = directory === "src/tests/pages" || directory.startsWith("src/tests/pages/");
148
+ const inTests = directory.split("/").includes("__tests__") || inPages;
149
+ if (isTestSupportModule(normalized)) return void 0;
150
+ if (!/\.(?:test|spec|test-d)\.[cm]?[jt]sx?$/u.test(basename) && !inTests) return;
151
+ const expected = expectedSubjects(directory);
152
+ const dotted = dottedSubject({
153
+ expected,
154
+ basename
155
+ });
156
+ const candidate = dotted ? `${DOTTED_SUBJECT}${basename.slice(dotted.length)}` : basename;
157
+ const match = normalized.startsWith("tooling/") ? /^(?<subject>[^.]+)(?<qualifiers>(?:\.[a-z][a-zA-Z0-9]*)*)\.test\.ts$/u.exec(candidate) : /^(?<subject>[^.]+)(?<qualifiers>(?:\.[a-z][a-zA-Z0-9]*)*)(?:\.(?<environment>node|dom|browser|e2e)\.test|\.test-d)\.tsx?$/u.exec(candidate);
158
+ if (!match?.groups) return "In src/, name the test Name[.qualifier].<node|dom|browser|e2e>.test.ts(x), or Name[.qualifier].test-d.ts(x) for a type test. In tooling/, use Name[.qualifier].test.ts.";
159
+ const { environment } = match.groups;
160
+ const subject = dotted ?? match.groups.subject;
161
+ if (inPages && environment !== "e2e") return "Page tests must use the e2e environment.";
162
+ if (environment === "e2e" && (!inPages || !subjects[directory])) return "Place e2e tests in src/tests/pages/<Page>/. The accepted pages are listed in @evertondgn/solid-tooling; for another page, turn off project/test-filename for its folder in lint.overrides.";
163
+ if (!expected) return "Place the test in a __tests__ folder inside the module folder, as in Button/__tests__/Button.dom.test.tsx. For a test folder without a module, turn off project/test-filename for its files in lint.overrides.";
164
+ if (!subject || !expected.includes(subject)) return `Use the exact name of the subject under test: ${expected.join(", ")}. Qualifiers use a dot and camelCase.`;
165
+ }
166
+ const testFilename = defineRule({
167
+ meta: {
168
+ type: "problem",
169
+ schema: []
170
+ },
171
+ create(context) {
172
+ return { Program(node) {
173
+ const message = testFilenameError(path.relative(context.cwd, context.filename));
174
+ if (message) context.report({
175
+ node,
176
+ message
177
+ });
178
+ } };
179
+ }
180
+ });
181
+ // src/project-plugin.ts
182
+ const cssModulesImport = defineRule({
183
+ meta: {
184
+ type: "suggestion",
185
+ schema: [],
186
+ messages: { useS: "Import CSS Modules as S: import S from \"./styles.module.css\"." }
187
+ },
188
+ create(context) {
189
+ return { ImportDeclaration(node) {
190
+ if (!/\.module\.css(?:[?#].*)?$/u.test(node.source.value)) return;
191
+ const [specifier] = node.specifiers;
192
+ if (node.specifiers.length !== 1 || specifier?.type !== "ImportDefaultSpecifier" || specifier.local.name !== "S") context.report({
193
+ node,
194
+ messageId: "useS"
195
+ });
196
+ } };
197
+ }
198
+ });
199
+ const plugin = definePlugin({
200
+ meta: { name: "project" },
201
+ rules: {
202
+ "css-modules-import": cssModulesImport,
203
+ "css-filename": cssFilename,
204
+ "test-filename": testFilename,
205
+ "no-export-list": noExportList,
206
+ "seo-module": seoModule
207
+ }
208
+ });
209
+ export { plugin as default };
@@ -0,0 +1,2 @@
1
+ import eslint_plugin_solid_default from "eslint-plugin-solid";
2
+ export { eslint_plugin_solid_default as default };
@@ -0,0 +1 @@
1
+ export { default } from "eslint-plugin-solid";
package/package.json CHANGED
@@ -1,6 +1,58 @@
1
1
  {
2
2
  "name": "@evertondgn/solid-tooling",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Lint, formatter and TypeScript presets for SolidJS applications built with Vite+.",
5
+ "author": "Éverton Toffanetto",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/everton-dgn/solidjs_base.git",
10
+ "directory": "packages/solid-tooling"
11
+ },
12
+ "keywords": [
13
+ "solidjs",
14
+ "vite-plus",
15
+ "oxlint",
16
+ "lint",
17
+ "tsconfig"
18
+ ],
19
+ "type": "module",
20
+ "files": [
21
+ "dist",
22
+ "tsconfig.base.json"
23
+ ],
24
+ "exports": {
25
+ "./architecture-plugin": "./dist/architecture-plugin.mjs",
26
+ "./backend-plugin": "./dist/backend-plugin.mjs",
27
+ "./css-filename": "./dist/css-filename.mjs",
28
+ "./fmt": "./dist/fmt.mjs",
29
+ "./lint": "./dist/lint.mjs",
30
+ "./project-plugin": "./dist/project-plugin.mjs",
31
+ "./solid-plugin": "./dist/solid-plugin.mjs",
32
+ "./package.json": "./package.json",
33
+ "./tsconfig.base.json": "./tsconfig.base.json"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "scripts": {
39
+ "build": "vp pack",
40
+ "dev": "vp pack --watch",
41
+ "test": "vp run test-rules",
42
+ "test:tarball": "vp run test-tarball"
43
+ },
44
+ "dependencies": {
45
+ "eslint-plugin-solid": "0.19.0"
46
+ },
47
+ "peerDependencies": {
48
+ "@inlang/paraglide-js": "3.2.2",
49
+ "vite-plus": "1.6.0"
50
+ },
51
+ "devDependencies": {
52
+ "@inlang/paraglide-js": "3.2.2",
53
+ "@types/node": "24.19.2",
54
+ "typescript": "7.0.2",
55
+ "valibot": "1.5.0",
56
+ "vite-plus": "1.6.0"
57
+ }
58
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2024",
4
+ "lib": ["ES2024", "DOM"],
5
+ "module": "esnext",
6
+ "moduleResolution": "bundler",
7
+ "noUncheckedIndexedAccess": true,
8
+ "noImplicitReturns": true,
9
+ "noImplicitOverride": true,
10
+ "noFallthroughCasesInSwitch": true,
11
+ "noUnusedLocals": true,
12
+ "noUnusedParameters": true,
13
+ "allowUnreachableCode": false,
14
+ "allowUnusedLabels": false,
15
+ "skipLibCheck": true,
16
+ "resolveJsonModule": true,
17
+ "moduleDetection": "force",
18
+ "verbatimModuleSyntax": true,
19
+ "noEmit": true,
20
+ "allowImportingTsExtensions": true,
21
+ "allowArbitraryExtensions": true,
22
+ "erasableSyntaxOnly": true,
23
+ "jsx": "preserve",
24
+ "jsxImportSource": "@solidjs/web"
25
+ }
26
+ }