@evertondgn/solid-tooling 0.0.0-stage → 0.1.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: "Declare o export junto da declaração (export function, export const, export type) em vez de usar export { ... }. Lista de exports só vale em reexport com 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: "O seo.ts exporta só o objeto seo. O corpo da página fica no index.tsx, porque o seo.ts entra no bundle inicial pelo route (AGENTS.md, Módulos de página).",
36
+ localImport: "O seo.ts não importa módulos locais como valor: o que ele importa entra no bundle inicial junto com o route. Mantenha o corpo da página no index.tsx (AGENTS.md, Módulos de página).",
37
+ computedImport: "O seo.ts não usa import() com caminho calculado: a regra não consegue conferir se o destino é local. Mantenha o corpo da página no index.tsx (AGENTS.md, Módulos de página).",
38
+ globImport: "O seo.ts não usa import.meta.glob: o Vite o converte em imports dos módulos que o padrão casa. Mantenha o corpo da página no index.tsx (AGENTS.md, Módulos de página)."
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 "Use Nome[.qualificador].ambiente.test.ts(x) ou Nome[.qualificador].test-d.ts(x) para tipos em src, ou Nome[.qualificador].test.ts em tooling.";
159
+ const { environment } = match.groups;
160
+ const subject = dotted ?? match.groups.subject;
161
+ if (inPages && environment !== "e2e") return "Testes de páginas devem usar o ambiente e2e.";
162
+ if (environment === "e2e" && (!inPages || !subjects[directory])) return "Coloque testes e2e em src/tests/pages/NomeDaPagina e declare a página no mapeamento.";
163
+ if (!expected) return "Coloque o teste em __tests__ junto ao módulo ou declare sua suíte no mapeamento de tooling/test-filename.ts.";
164
+ if (!subject || !expected.includes(subject)) return `Use o nome exato do assunto testado: ${expected.join(", ")}. Qualificadores usam ponto e 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: "Importe CSS Modules como 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,59 @@
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.1.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
+ "prepublishOnly": "vp pack"
44
+ },
45
+ "dependencies": {
46
+ "eslint-plugin-solid": "0.19.0"
47
+ },
48
+ "peerDependencies": {
49
+ "@inlang/paraglide-js": "3.2.2",
50
+ "vite-plus": "1.6.0"
51
+ },
52
+ "devDependencies": {
53
+ "@inlang/paraglide-js": "3.2.2",
54
+ "@types/node": "24.19.2",
55
+ "typescript": "7.0.2",
56
+ "valibot": "1.5.0",
57
+ "vite-plus": "1.6.0"
58
+ }
59
+ }
@@ -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
+ }