@polariens/kitsune-lint 1.0.0-rc.3

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/README.md ADDED
@@ -0,0 +1,233 @@
1
+ # @merieli/kitsune-lint 🩊
2
+
3
+ Opinionated ESLint & Prettier configs for high-quality Vue, TypeScript & Vitest projects.
4
+
5
+ > **Kitsune** (狐) Ă© a raposa mĂ­stica do folclore japonĂȘs — astuta, adaptĂĄvel e capaz de se transformar conforme o contexto. Assim como a kitsune, este pacote se molda ao seu projeto: vocĂȘ escolhe os mĂłdulos e as opçÔes, e ele compĂ”e as regras certas para cada cenĂĄrio. CĂłdigo limpo com a precisĂŁo de uma raposa.
6
+
7
+ ## O que Ă©
8
+
9
+ Um pacote instalĂĄvel que padroniza regras de linting e formatação entre mĂșltiplos projetos. As configuraçÔes sĂŁo **modulares** — cada conjunto de regras (TypeScript, Vue, Pinia, segurança, etc.) Ă© independente e configurĂĄvel via opçÔes.
10
+
11
+ ## Como usar
12
+
13
+ ### Instalação
14
+
15
+ ```bash
16
+ npm install --save-dev @merieli/kitsune-lint
17
+ ```
18
+
19
+ As peer dependencies devem ser instaladas no projeto consumidor:
20
+
21
+ ```bash
22
+ npm install --save-dev eslint @eslint/js typescript-eslint globals
23
+ ```
24
+
25
+ Dependendo dos módulos habilitados, instale também:
26
+
27
+ | MĂłdulo | DependĂȘncias extras |
28
+ | ---------- | ------------------------------------------------ |
29
+ | `security` | `eslint-plugin-security` |
30
+ | `vue` | `eslint-plugin-vue vue-eslint-parser` |
31
+ | `pinia` | `eslint-plugin-pinia` |
32
+ | `vitest` | `@vitest/eslint-plugin` |
33
+
34
+ ### ESLint — Factory function (recomendado)
35
+
36
+ A forma mais simples. O `createConfig` compÔe os módulos selecionados:
37
+
38
+ ```javascript
39
+ // eslint.config.js
40
+ import { createConfig } from '@merieli/kitsune-lint/eslint';
41
+
42
+ export default await createConfig({
43
+ vue: true,
44
+ pinia: true,
45
+ tests: true,
46
+ vitest: true,
47
+ });
48
+ ```
49
+
50
+ Por padrĂŁo, `base`, `typescript`, `security` e `cleanCode` jĂĄ vĂȘm habilitados. Passe `false` para desativar:
51
+
52
+ ```javascript
53
+ export default await createConfig({
54
+ security: false,
55
+ vue: true,
56
+ });
57
+ ```
58
+
59
+ ### ESLint — Customizando módulos
60
+
61
+ Cada módulo aceita um objeto de opçÔes no lugar de `true`:
62
+
63
+ ```javascript
64
+ export default await createConfig({
65
+ base: { environment: 'node' },
66
+ cleanCode: { maxDepth: 3, maxParams: 3, complexity: 10, maxLines: 300 },
67
+ vue: { apiStyle: 'composition' },
68
+ pinia: true,
69
+ vitest: {
70
+ titlePattern: '^should .+',
71
+ titleMessage: 'Test title must start with "should"',
72
+ fn: 'it',
73
+ maxNestedDescribe: 2,
74
+ },
75
+ });
76
+ ```
77
+
78
+ ### ESLint — Imports granulares
79
+
80
+ Para controle total, importe cada mĂłdulo diretamente:
81
+
82
+ ```javascript
83
+ // eslint.config.js
84
+ import { base } from '@merieli/kitsune-lint/eslint/base';
85
+ import { typescript } from '@merieli/kitsune-lint/eslint/typescript';
86
+ import { vue } from '@merieli/kitsune-lint/eslint/vue';
87
+ import { pinia } from '@merieli/kitsune-lint/eslint/pinia';
88
+
89
+ export default [
90
+ ...base(),
91
+ ...typescript(),
92
+ ...vue(),
93
+ ...(await pinia()),
94
+ ];
95
+ ```
96
+
97
+ ### ESLint — Estendendo com configs extras
98
+
99
+ Use `extend` para adicionar configs de plugins externos (ex: `eslint-config-prettier`):
100
+
101
+ ```javascript
102
+ import { createConfig } from '@merieli/kitsune-lint/eslint';
103
+ import eslintConfigPrettier from 'eslint-config-prettier';
104
+ import pluginVue from 'eslint-plugin-vue';
105
+
106
+ export default await createConfig({
107
+ vue: true,
108
+ pinia: true,
109
+ tests: true,
110
+ vitest: true,
111
+ extend: [
112
+ ...pluginVue.configs['flat/recommended'],
113
+ eslintConfigPrettier,
114
+ ],
115
+ });
116
+ ```
117
+
118
+ ### Prettier
119
+
120
+ Uso direto da config padrĂŁo:
121
+
122
+ ```javascript
123
+ // prettier.config.mjs
124
+ import { prettierConfig } from '@merieli/kitsune-lint/prettier';
125
+ export default prettierConfig;
126
+ ```
127
+
128
+ Com overrides:
129
+
130
+ ```javascript
131
+ import { createPrettierConfig } from '@merieli/kitsune-lint/prettier';
132
+ export default createPrettierConfig({ printWidth: 120 });
133
+ ```
134
+
135
+ ## MĂłdulos
136
+
137
+ | Módulo | Default | Descrição |
138
+ | ------------ | ------- | ------------------------------------------------------------ |
139
+ | `base` | ✅ on | Globals do ambiente (browser/node) |
140
+ | `typescript` | ✅ on | Regras TS recomendadas + naming conventions |
141
+ | `security` | ✅ on | Prevenção de eval, XSS, injection + eslint-plugin-security |
142
+ | `cleanCode` | ✅ on | SRP, legibilidade, imutabilidade, organização |
143
+ | `vue` | ❌ off | Vue 3 + script setup + type-based props/emits |
144
+ | `pinia` | ❌ off | Stores Pinia — naming, organização, boas práticas |
145
+ | `tests` | ❌ off | Relaxamentos para arquivos de teste (desliga regras rígidas) |
146
+ | `vitest` | ❌ off | Regras do plugin Vitest (naming, hooks, describe) |
147
+
148
+ ## OpçÔes dos módulos
149
+
150
+ ### base
151
+
152
+ | Opção | Padrão | Descrição |
153
+ | ------------- | ----------- | ------------------------------------------------ |
154
+ | `environment` | `'browser'` | Globals: `browser`, `node`, `shared-node-browser`|
155
+
156
+ ### typescript
157
+
158
+ | Opção | Padrão | Descrição |
159
+ | -------- | -------------- | ------------------ |
160
+ | `ignores`| configs + dist | Patterns a ignorar |
161
+ | `rules` | `{}` | Regras extras |
162
+
163
+ ### security
164
+
165
+ | Opção | Padrão | Descrição |
166
+ | --------------- | ------ | ----------------------------- |
167
+ | `pluginEnabled` | `true` | Usar `eslint-plugin-security` |
168
+ | `rules` | `{}` | Regras extras |
169
+
170
+ ### cleanCode
171
+
172
+ | Opção | Padrão | Descrição |
173
+ | --------------------- | ------ | ---------------------------------- |
174
+ | `maxDepth` | `4` | Profundidade mĂĄxima de aninhamento |
175
+ | `maxParams` | `4` | Parùmetros måximos por função |
176
+ | `complexity` | `14` | Complexidade ciclomĂĄtica mĂĄxima |
177
+ | `maxLines` | `400` | Linhas mĂĄximas por arquivo |
178
+ | `maxLinesPerFunction` | `80` | Linhas måximas por função |
179
+ | `rules` | `{}` | Regras extras |
180
+
181
+ ### vue
182
+
183
+ | Opção | Padrão | Descrição |
184
+ | ---------- | ---------------- | ------------------- |
185
+ | `apiStyle` | `'script-setup'` | Estilo de API Vue |
186
+ | `rules` | `{}` | Regras extras |
187
+
188
+ ### pinia
189
+
190
+ | Opção | Padrão | Descrição |
191
+ | ------- | ------------------------- | ------------------------------- |
192
+ | `files` | `['src/state/**/*.ts']` | Override dos file patterns |
193
+ | `rules` | `{}` | Regras extras |
194
+
195
+ ### tests
196
+
197
+ Relaxamentos para arquivos de teste — desliga regras rígidas de produção como `complexity`, `max-lines`, `max-lines-per-function`, `no-explicit-any`, `naming-convention`, etc.
198
+
199
+ | Opção | Padrão | Descrição |
200
+ | ------- | ------------------------------- | -------------------------- |
201
+ | `files` | `['tests/**/*.{js,mjs,cjs,ts}']`| Override dos file patterns |
202
+ | `rules` | `{}` | Regras extras |
203
+
204
+ ### vitest
205
+
206
+ | Opção | Padrão | Descrição |
207
+ | ------------------ | -------------- | -------------------------------- |
208
+ | `fn` | `'test'` | `test()` ou `it()` |
209
+ | `titlePattern` | Gherkin PT-BR | Regex do tĂ­tulo dos testes |
210
+ | `titleMessage` | mensagem PT-BR | Mensagem de erro |
211
+ | `maxNestedDescribe`| `3` | MĂĄximo de describe aninhados |
212
+ | `rules` | `{}` | Regras extras |
213
+
214
+ ## Estrutura
215
+
216
+ ```
217
+ @merieli/kitsune-lint/
218
+ ├── package.json
219
+ ├── eslint/
220
+ │ ├── index.mjs # Factory createConfig + re-exports
221
+ │ ├── utils.mjs # Patterns de arquivos compartilhados
222
+ │ └── configs/
223
+ │ ├── base.mjs # Globals do ambiente (browser/node)
224
+ │ ├── typescript.mjs # Regras TypeScript + naming conventions
225
+ │ ├── security.mjs # Prevenção de eval, XSS, injection
226
+ │ ├── clean-code.mjs # SRP, legibilidade, imutabilidade
227
+ │ ├── vue.mjs # Vue 3 + script setup
228
+ │ ├── pinia.mjs # Stores Pinia
229
+ │ ├── tests.mjs # Relaxamentos para testes
230
+ │ └── vitest.mjs # Regras plugin Vitest
231
+ └── prettier/
232
+ └── index.mjs # Config Prettier exportável
233
+ ```
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { cpSync, existsSync, readFileSync } from 'node:fs';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const source = resolve(__dirname, '..', 'prettier', '.prettierignore');
9
+ const dest = resolve(process.cwd(), '.prettierignore');
10
+
11
+ const force = process.argv.includes('--force');
12
+
13
+ if (existsSync(dest) && !force) {
14
+ const existing = readFileSync(dest, 'utf-8');
15
+ const template = readFileSync(source, 'utf-8');
16
+
17
+ if (existing === template) {
18
+ console.info('[lint-config] .prettierignore jĂĄ estĂĄ atualizado.');
19
+ process.exit(0);
20
+ }
21
+
22
+ console.error(
23
+ '[lint-config] .prettierignore jĂĄ existe e difere do template.\n' +
24
+ ' Use --force para sobrescrever.'
25
+ );
26
+ process.exit(1);
27
+ }
28
+
29
+ cpSync(source, dest);
30
+ console.info(`[lint-config] .prettierignore copiado para ${dest}`);
@@ -0,0 +1,10 @@
1
+ import type { Linter } from 'eslint';
2
+
3
+ export interface BaseOptions {
4
+ /** File patterns override */
5
+ files?: string[];
6
+ /** Ambiente de globals a aplicar @default 'browser' */
7
+ environment?: 'browser' | 'node' | 'shared-node-browser' | 'worker' | 'serviceworker';
8
+ }
9
+
10
+ export declare function base(options?: BaseOptions): Linter.Config[];
@@ -0,0 +1,28 @@
1
+ import globals from 'globals';
2
+
3
+ import { resolveFiles } from '../utils.mjs';
4
+
5
+ /**
6
+ * @typedef {Object} BaseOptions
7
+ * @property {string[]} [files] - File patterns override
8
+ * @property {'browser' | 'node' | 'shared-node-browser'} [environment='browser'] - Ambiente global
9
+ */
10
+
11
+ /**
12
+ * Configuração base com globals do ambiente.
13
+ * @param {BaseOptions} [options={}]
14
+ * @returns {import('eslint').Linter.Config[]}
15
+ */
16
+ export function base(options = {}) {
17
+ const { files, environment = 'browser' } = options;
18
+
19
+ return [
20
+ {
21
+ files: resolveFiles('all', files),
22
+ name: '@kitsune/base/globals',
23
+ languageOptions: {
24
+ globals: globals[environment],
25
+ },
26
+ },
27
+ ];
28
+ }
@@ -0,0 +1,20 @@
1
+ import type { Linter } from 'eslint';
2
+
3
+ export interface CleanCodeOptions {
4
+ /** File patterns override */
5
+ files?: string[];
6
+ /** Profundidade mĂĄxima de aninhamento @default 4 */
7
+ maxDepth?: number;
8
+ /** NĂșmero mĂĄximo de parĂąmetros por função @default 4 */
9
+ maxParams?: number;
10
+ /** Complexidade ciclomĂĄtica mĂĄxima @default 14 */
11
+ complexity?: number;
12
+ /** Linhas mĂĄximas por arquivo @default 400 */
13
+ maxLines?: number;
14
+ /** Linhas måximas por função @default 80 */
15
+ maxLinesPerFunction?: number;
16
+ /** Regras adicionais ou overrides */
17
+ rules?: Partial<Linter.RulesRecord>;
18
+ }
19
+
20
+ export declare function cleanCode(options?: CleanCodeOptions): Linter.Config[];
@@ -0,0 +1,77 @@
1
+ import { resolveFiles } from '../utils.mjs';
2
+
3
+ /**
4
+ * @typedef {Object} CleanCodeOptions
5
+ * @property {string[]} [files] - File patterns override
6
+ * @property {number} [maxDepth=4] - Profundidade mĂĄxima de aninhamento
7
+ * @property {number} [maxParams=4] - NĂșmero mĂĄximo de parĂąmetros
8
+ * @property {number} [complexity=14] - Complexidade ciclomĂĄtica mĂĄxima
9
+ * @property {number} [maxLines=400] - Linhas mĂĄximas por arquivo
10
+ * @property {number} [maxLinesPerFunction=80] - Linhas måximas por função
11
+ * @property {Record<string, unknown>} [rules] - Regras adicionais ou overrides
12
+ */
13
+
14
+ /**
15
+ * Regras de clean code para legibilidade e manutenibilidade.
16
+ * @param {CleanCodeOptions} [options={}]
17
+ * @returns {import('eslint').Linter.Config[]}
18
+ */
19
+ export function cleanCode(options = {}) {
20
+ const {
21
+ files,
22
+ maxDepth = 4,
23
+ maxParams = 4,
24
+ complexity: maxComplexity = 14,
25
+ maxLines = 400,
26
+ maxLinesPerFunction = 80,
27
+ rules: extraRules = {},
28
+ } = options;
29
+
30
+ return [
31
+ {
32
+ files: resolveFiles('all', files),
33
+ name: '@kitsune/clean-code/rules',
34
+ rules: {
35
+ // --- Single Responsibility (SRP) ---
36
+ complexity: ['warn', maxComplexity],
37
+ 'max-lines': ['warn', { max: maxLines, skipBlankLines: true, skipComments: true }],
38
+ 'max-lines-per-function': ['warn', { max: maxLinesPerFunction, skipBlankLines: true, skipComments: true }],
39
+ 'max-depth': ['error', maxDepth],
40
+ 'max-params': ['error', maxParams],
41
+
42
+ // --- Readability & Expressiveness ---
43
+ 'no-negated-condition': 'error',
44
+ 'no-nested-ternary': 'error',
45
+ 'no-unneeded-ternary': 'error',
46
+ 'no-else-return': 'error',
47
+ 'no-lonely-if': 'error',
48
+ 'prefer-template': 'error',
49
+ 'object-shorthand': 'error',
50
+ curly: ['error', 'all'],
51
+ 'arrow-body-style': ['error', 'as-needed'],
52
+ 'no-multi-assign': 'error',
53
+ 'no-unreachable-loop': 'error',
54
+ 'no-unused-labels': 'error',
55
+
56
+ // --- Immutability & Safety ---
57
+ 'no-param-reassign': ['error', { props: false }],
58
+ 'no-return-assign': ['error', 'always'],
59
+ 'no-sequences': 'error',
60
+ 'no-constructor-return': 'error',
61
+ 'no-promise-executor-return': 'error',
62
+ // Evita modificação de built-ins
63
+ 'no-extend-native': 'error',
64
+
65
+ // --- Code Organization ---
66
+ 'default-case-last': 'error',
67
+ 'grouped-accessor-pairs': ['error', 'getBeforeSet'],
68
+
69
+ // --- Bug Prevention ---
70
+ 'no-template-curly-in-string': 'warn',
71
+ 'no-useless-return': 'error',
72
+
73
+ ...extraRules,
74
+ },
75
+ },
76
+ ];
77
+ }
@@ -0,0 +1,10 @@
1
+ import type { Linter } from 'eslint';
2
+
3
+ export interface PiniaOptions {
4
+ /** File patterns override @default ['src/state/**\/*.ts'] */
5
+ files?: string[];
6
+ /** Regras adicionais ou overrides */
7
+ rules?: Partial<Linter.RulesRecord>;
8
+ }
9
+
10
+ export declare function pinia(options?: PiniaOptions): Promise<Linter.Config[]>;
@@ -0,0 +1,36 @@
1
+ import { resolveFiles } from '../utils.mjs';
2
+
3
+ /**
4
+ * @typedef {Object} PiniaOptions
5
+ * @property {string[]} [files] - File patterns override (default: src/state/**\/*.ts)
6
+ * @property {Record<string, unknown>} [rules] - Regras adicionais ou overrides
7
+ */
8
+
9
+ /**
10
+ * Regras para stores Pinia — organização, naming e boas práticas.
11
+ * @param {PiniaOptions} [options={}]
12
+ * @returns {Promise<import('eslint').Linter.Config[]>}
13
+ */
14
+ export async function pinia(options = {}) {
15
+ const { files, rules: extraRules = {} } = options;
16
+
17
+ const pluginPinia = await import('eslint-plugin-pinia').then((m) => m.default ?? m);
18
+
19
+ return [
20
+ {
21
+ files: resolveFiles('pinia', files),
22
+ name: '@kitsune/pinia/rules',
23
+ plugins: { pinia: pluginPinia },
24
+ rules: {
25
+ 'pinia/never-export-initialized-store': 'error',
26
+ 'pinia/no-duplicate-store-ids': 'error',
27
+ 'pinia/no-return-global-properties': 'error',
28
+ 'pinia/no-store-to-refs-in-store': 'error',
29
+ 'pinia/prefer-single-store-per-file': 'warn',
30
+ 'pinia/prefer-use-store-naming-convention': 'warn',
31
+ 'pinia/require-setup-store-properties-export': 'warn',
32
+ ...extraRules,
33
+ },
34
+ },
35
+ ];
36
+ }
@@ -0,0 +1,12 @@
1
+ import type { Linter } from 'eslint';
2
+
3
+ export interface SecurityOptions {
4
+ /** File patterns override */
5
+ files?: string[];
6
+ /** Habilitar eslint-plugin-security @default true */
7
+ pluginEnabled?: boolean;
8
+ /** Regras adicionais ou overrides */
9
+ rules?: Partial<Linter.RulesRecord>;
10
+ }
11
+
12
+ export declare function security(options?: SecurityOptions): Linter.Config[];
@@ -0,0 +1,46 @@
1
+ import pluginSecurity from 'eslint-plugin-security';
2
+
3
+ import { resolveFiles } from '../utils.mjs';
4
+
5
+ /**
6
+ * @typedef {Object} SecurityOptions
7
+ * @property {string[]} [files] - File patterns override
8
+ * @property {boolean} [pluginEnabled=true] - Habilitar eslint-plugin-security
9
+ * @property {Record<string, unknown>} [rules] - Regras adicionais ou overrides
10
+ */
11
+
12
+ /**
13
+ * Regras de segurança para prevenir vulnerabilidades comuns.
14
+ * @param {SecurityOptions} [options={}]
15
+ * @returns {import('eslint').Linter.Config[]}
16
+ */
17
+ export function security(options = {}) {
18
+ const { files, pluginEnabled = true, rules: extraRules = {} } = options;
19
+ const resolvedFiles = resolveFiles('all', files);
20
+
21
+ const configs = []
22
+
23
+ if (pluginEnabled) {
24
+ configs.push({
25
+ ...pluginSecurity.configs.recommended,
26
+ files: resolvedFiles,
27
+ name: '@kitsune/security/plugin',
28
+ });
29
+ }
30
+
31
+ configs.push({
32
+ files: resolvedFiles,
33
+ name: '@kitsune/security/rules',
34
+ rules: {
35
+ 'no-alert': 'error',
36
+ 'no-eval': 'error',
37
+ 'no-debugger': 'error',
38
+ 'no-new-func': 'error',
39
+ 'no-script-url': 'error',
40
+ 'no-implied-eval': 'error',
41
+ ...extraRules,
42
+ },
43
+ })
44
+
45
+ return configs;
46
+ }
@@ -0,0 +1,10 @@
1
+ import type { Linter } from 'eslint';
2
+
3
+ export interface TestsOptions {
4
+ /** File patterns override */
5
+ files?: string[];
6
+ /** Regras adicionais ou overrides */
7
+ rules?: Partial<Linter.RulesRecord>;
8
+ }
9
+
10
+ export declare function tests(options?: TestsOptions): Linter.Config[];
@@ -0,0 +1,40 @@
1
+ import { resolveFiles } from '../utils.mjs';
2
+
3
+ /**
4
+ * @typedef {Object} TestsOptions
5
+ * @property {string[]} [files] - File patterns override
6
+ * @property {Record<string, unknown>} [rules] - Regras adicionais ou overrides
7
+ */
8
+
9
+ /**
10
+ * Relaxamentos para arquivos de teste — desliga regras rígidas de produção
11
+ * que atrapalham a escrita de testes (complexity, max-lines, naming, etc).
12
+ * @param {TestsOptions} [options={}]
13
+ * @returns {import('eslint').Linter.Config[]}
14
+ */
15
+ export function tests(options = {}) {
16
+ const { files, rules: extraRules = {} } = options;
17
+
18
+ return [
19
+ {
20
+ files: resolveFiles('tests', files),
21
+ name: '@kitsune/tests/relaxations',
22
+ rules: {
23
+ 'max-lines-per-function': 'off',
24
+ 'max-lines': 'off',
25
+ complexity: 'off',
26
+ '@typescript-eslint/no-shadow': 'off',
27
+ '@typescript-eslint/no-non-null-assertion': 'off',
28
+ '@typescript-eslint/no-explicit-any': 'off',
29
+ '@typescript-eslint/naming-convention': 'off',
30
+ 'no-promise-executor-return': 'off',
31
+ 'no-param-reassign': 'off',
32
+ 'no-constructor-return': 'off',
33
+ 'no-script-url': 'off',
34
+ 'security/detect-object-injection': 'off',
35
+ 'security/detect-unsafe-regex': 'off',
36
+ ...extraRules,
37
+ },
38
+ },
39
+ ];
40
+ }
@@ -0,0 +1,12 @@
1
+ import type { Linter } from 'eslint';
2
+
3
+ export interface TypescriptOptions {
4
+ /** File patterns override */
5
+ files?: string[];
6
+ /** Patterns a ignorar */
7
+ ignores?: string[];
8
+ /** Regras adicionais ou overrides */
9
+ rules?: Partial<Linter.RulesRecord>;
10
+ }
11
+
12
+ export declare function typescript(options?: TypescriptOptions): Linter.Config[];