feat: add ESLint configuration for vertical spacing and stylistic rules
- Introduced `.lintrc` for managing vertical spacing rules in the codebase. - Updated `eslint.config.mjs` to integrate the new spacing rules with ESLint. - Created documentation on code formatting and spacing guidelines in `formato-y-espaciado-del-codigo.md`. - Implemented tests in `spacing.test.mjs` to ensure spacing rules are enforced correctly without altering syntax.
This commit is contained in:
parent
dbac5dfa84
commit
2c827b8a7d
124
.lintrc
Normal file
124
.lintrc
Normal file
@ -0,0 +1,124 @@
|
||||
{
|
||||
"rules": {
|
||||
"@stylistic/padding-line-between-statements": [
|
||||
"error",
|
||||
{
|
||||
"blankLine": "always",
|
||||
"prev": "*",
|
||||
"next": [
|
||||
"function",
|
||||
"class",
|
||||
"interface",
|
||||
"type",
|
||||
"enum",
|
||||
"export",
|
||||
{
|
||||
"selector": "VariableDeclaration:has(VariableDeclarator > :matches(ArrowFunctionExpression, FunctionExpression))"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"blankLine": "always",
|
||||
"prev": [
|
||||
"function",
|
||||
"class",
|
||||
"interface",
|
||||
"type",
|
||||
"enum",
|
||||
"export",
|
||||
{
|
||||
"selector": "VariableDeclaration:has(VariableDeclarator > :matches(ArrowFunctionExpression, FunctionExpression))"
|
||||
}
|
||||
],
|
||||
"next": "*"
|
||||
},
|
||||
{
|
||||
"blankLine": "always",
|
||||
"prev": "import",
|
||||
"next": "*"
|
||||
},
|
||||
{
|
||||
"blankLine": "any",
|
||||
"prev": "import",
|
||||
"next": "import"
|
||||
},
|
||||
{
|
||||
"blankLine": "always",
|
||||
"prev": [
|
||||
"const",
|
||||
"let",
|
||||
"var"
|
||||
],
|
||||
"next": "*"
|
||||
},
|
||||
{
|
||||
"blankLine": "any",
|
||||
"prev": [
|
||||
"const",
|
||||
"let",
|
||||
"var"
|
||||
],
|
||||
"next": [
|
||||
"const",
|
||||
"let",
|
||||
"var"
|
||||
]
|
||||
},
|
||||
{
|
||||
"blankLine": "always",
|
||||
"prev": "*",
|
||||
"next": "return"
|
||||
},
|
||||
{
|
||||
"blankLine": "always",
|
||||
"prev": {
|
||||
"selector": "VariableDeclaration:has(VariableDeclarator > :matches(ArrowFunctionExpression, FunctionExpression))"
|
||||
},
|
||||
"next": "*"
|
||||
},
|
||||
{
|
||||
"blankLine": "always",
|
||||
"prev": "*",
|
||||
"next": {
|
||||
"selector": "VariableDeclaration:has(VariableDeclarator > :matches(ArrowFunctionExpression, FunctionExpression))"
|
||||
}
|
||||
},
|
||||
{
|
||||
"blankLine": "any",
|
||||
"prev": "function-overload",
|
||||
"next": [
|
||||
"function-overload",
|
||||
"function"
|
||||
]
|
||||
}
|
||||
],
|
||||
"@stylistic/lines-between-class-members": [
|
||||
"error",
|
||||
{
|
||||
"enforce": [
|
||||
{
|
||||
"blankLine": "always",
|
||||
"prev": "method",
|
||||
"next": "*"
|
||||
},
|
||||
{
|
||||
"blankLine": "always",
|
||||
"prev": "*",
|
||||
"next": "method"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"exceptAfterOverload": true
|
||||
}
|
||||
],
|
||||
"@stylistic/no-multiple-empty-lines": [
|
||||
"error",
|
||||
{
|
||||
"max": 1,
|
||||
"maxBOF": 0,
|
||||
"maxEOF": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -13,6 +13,8 @@ No imprimir, copiar ni versionar .env locales o secretos.
|
||||
|
||||
## Convenciones
|
||||
|
||||
El espaciado vertical se define en `.lintrc`, cargado por `eslint.config.mjs`. Separar funciones, clases y métodos con una línea vacía; mantener agrupadas propiedades y firmas de sobrecarga. `pnpm format` combina Biome con estas reglas. Véase [formato y espaciado](docs/formato-y-espaciado-del-codigo.md).
|
||||
|
||||
- Código e identificadores en inglés; comentarios, TSDoc y documentación en castellano.
|
||||
- Ficheros y directorios en kebab-case. Clases, tipos e interfaces en PascalCase.
|
||||
- Variables, funciones y métodos en camelCase; constantes globales en UPPER_SNAKE_CASE.
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
"ignoreUnknown": true,
|
||||
"includes": [
|
||||
"**",
|
||||
"!!packages/shadcn-ui",
|
||||
"!!**/node_modules",
|
||||
"!!**/.next",
|
||||
"!!**/dist",
|
||||
|
||||
66
docs/formato-y-espaciado-del-codigo.md
Normal file
66
docs/formato-y-espaciado-del-codigo.md
Normal file
@ -0,0 +1,66 @@
|
||||
# Formato y espaciado del código
|
||||
|
||||
Biome mantiene el formato general, la organización de imports y las reglas de calidad existentes. ESLint Stylistic complementa únicamente el espaciado vertical que Biome no inserta automáticamente. Las versiones están fijadas en package.json y pnpm-lock.yaml; el parser de TypeScript permite analizar TS, TSX y las sobrecargas sin añadir reglas de análisis duplicadas.
|
||||
|
||||
## Configuración
|
||||
|
||||
`.lintrc` es un JSON con las reglas de espaciado. No es un nombre de configuración que Biome o ESLint detecten por sí mismos: `eslint.config.mjs` lo carga y registra el plugin y el parser. Para ajustar la política, editar las reglas de `.lintrc`.
|
||||
|
||||
- Una línea vacía antes y después de funciones, clases, interfaces, tipos, enums y declaraciones exportadas.
|
||||
- Separación entre funciones declaradas mediante variables, incluidas funciones flecha.
|
||||
- Una línea vacía entre métodos de clase, constructor y accesores, y entre un grupo de propiedades y un método.
|
||||
- Las propiedades de clase pueden seguir agrupadas sin líneas vacías individuales.
|
||||
- Las firmas consecutivas de sobrecarga y su implementación se mantienen juntas.
|
||||
- Separación al terminar un grupo de variables y antes de return cuando existe una instrucción previa.
|
||||
- Como máximo una línea vacía consecutiva. Biome mantiene sus agrupaciones de imports.
|
||||
|
||||
El código de terceros de `packages/shadcn-ui`, los directorios generados, dependencias y declaraciones `.d.ts` quedan fuera del espaciado. La biblioteca vendor también queda excluida de Biome para respetar AGENTS.md.
|
||||
|
||||
## Comandos
|
||||
|
||||
```bash
|
||||
pnpm format # Biome y después espaciado vertical, en todo el código propio
|
||||
pnpm format:biome # Solo el formateador de Biome
|
||||
pnpm format:spacing # Aplicar únicamente el espaciado vertical
|
||||
pnpm lint:spacing # Comprobar espaciado sin modificar archivos
|
||||
pnpm test:spacing # Validar las reglas, la sintaxis y la compatibilidad con Biome
|
||||
```
|
||||
|
||||
También se puede actuar sobre un archivo concreto:
|
||||
|
||||
```bash
|
||||
pnpm exec eslint modules/assets/src/api/domain/asset.aggregate.ts --fix
|
||||
```
|
||||
|
||||
La incorporación de las reglas no reformatea automáticamente todo el código existente: `pnpm format:spacing` realiza esa adaptación cuando se ejecute. Los comandos habituales `lint` y `check` siguen siendo los de Biome; `lint:spacing` comprueba la política complementaria de forma explícita.
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```ts
|
||||
class Service {
|
||||
private enabled = true;
|
||||
private name = "Metis Desk";
|
||||
|
||||
constructor() {}
|
||||
|
||||
first() {
|
||||
const name = this.name;
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
second() {
|
||||
return this.enabled;
|
||||
}
|
||||
}
|
||||
|
||||
function first() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
function second() {
|
||||
return 2;
|
||||
}
|
||||
```
|
||||
|
||||
Fuentes de las reglas: [separación entre declaraciones](https://eslint.style/rules/padding-line-between-statements) y [separación entre miembros de clase](https://eslint.style/rules/lines-between-class-members).
|
||||
32
eslint.config.mjs
Normal file
32
eslint.config.mjs
Normal file
@ -0,0 +1,32 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
import stylistic from "@stylistic/eslint-plugin";
|
||||
import parser from "@typescript-eslint/parser";
|
||||
|
||||
// .lintrc contiene solo espaciado vertical; Biome conserva el resto de reglas.
|
||||
const spacing = JSON.parse(readFileSync(new URL("./.lintrc", import.meta.url), "utf8"));
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: [
|
||||
"**/node_modules/**",
|
||||
"**/dist/**",
|
||||
"**/build/**",
|
||||
"**/coverage/**",
|
||||
"**/.turbo/**",
|
||||
"**/out/**",
|
||||
"**/public/**",
|
||||
"**/*.d.ts",
|
||||
"packages/shadcn-ui/**",
|
||||
],
|
||||
},
|
||||
{
|
||||
files: ["**/*.{ts,tsx,js,jsx,mjs,cjs,mts,cts}"],
|
||||
languageOptions: {
|
||||
parser,
|
||||
parserOptions: { ecmaFeatures: { jsx: true } },
|
||||
},
|
||||
plugins: { "@stylistic": stylistic },
|
||||
rules: spacing.rules,
|
||||
},
|
||||
];
|
||||
11
package.json
11
package.json
@ -11,14 +11,21 @@
|
||||
"typecheck": "turbo run typecheck",
|
||||
"lint": "biome lint .",
|
||||
"check": "biome check .",
|
||||
"format": "biome format --write .",
|
||||
"format": "pnpm format:biome && pnpm format:spacing",
|
||||
"test": "turbo run test",
|
||||
"build": "turbo run build",
|
||||
"db:up": "python3 scripts/prepare-local-env.py && docker compose up -d --wait"
|
||||
"db:up": "python3 scripts/prepare-local-env.py && docker compose up -d --wait",
|
||||
"lint:spacing": "eslint .",
|
||||
"format:spacing": "eslint . --fix",
|
||||
"format:biome": "biome format --write .",
|
||||
"test:spacing": "node --test scripts/spacing.test.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.4.11",
|
||||
"@stylistic/eslint-plugin": "5.10.0",
|
||||
"@types/node": "25.6.0",
|
||||
"@typescript-eslint/parser": "8.71.1",
|
||||
"eslint": "10.12.0",
|
||||
"tsx": "4.21.0",
|
||||
"turbo": "2.9.14",
|
||||
"typescript": "6.0.2"
|
||||
|
||||
623
pnpm-lock.yaml
623
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
86
scripts/spacing.test.mjs
Normal file
86
scripts/spacing.test.mjs
Normal file
@ -0,0 +1,86 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { test } from "node:test";
|
||||
|
||||
import { parse } from "@typescript-eslint/parser";
|
||||
import { ESLint } from "eslint";
|
||||
|
||||
const eslint = new ESLint({ fix: true });
|
||||
|
||||
function syntax(source) {
|
||||
const tree = parse(source, { sourceType: "module", ecmaFeatures: { jsx: true } });
|
||||
|
||||
return JSON.stringify(tree, (key, value) =>
|
||||
["loc", "range", "start", "end", "tokens", "comments"].includes(key) ? undefined : value
|
||||
);
|
||||
}
|
||||
|
||||
const source = `import { helper } from "example";
|
||||
export interface Example { value: number }
|
||||
type Identifier = string;
|
||||
function first() { return 1; }
|
||||
function second() { return 2; }
|
||||
const arrow = () => 3;
|
||||
const other = () => 4;
|
||||
class Service {
|
||||
value = 1;
|
||||
enabled = true;
|
||||
constructor() {}
|
||||
first() { return this.value; }
|
||||
second() { return 2; }
|
||||
}
|
||||
function overloaded(value: string): string;
|
||||
function overloaded(value: number): number;
|
||||
function overloaded(value: string | number) { return value; }
|
||||
export function View() { const label = "Nombre"; return <span>{label}</span>; }
|
||||
`;
|
||||
|
||||
test("el espaciado separa declaraciones y métodos sin cambiar la sintaxis ni las sobrecargas", async () => {
|
||||
const [result] = await eslint.lintText(source, { filePath: "apps/web/src/spacing-example.tsx" });
|
||||
|
||||
assert.ok(result);
|
||||
assert.equal(result.errorCount, 0);
|
||||
assert.ok(result.output);
|
||||
assert.match(result.output, /}\n\nfunction second/);
|
||||
assert.match(result.output, /const arrow = .*;\n\nconst other/);
|
||||
assert.match(result.output, /enabled = true;\n\n {2}constructor/);
|
||||
assert.match(result.output, /constructor\(\) {}\n\n {2}first/);
|
||||
assert.match(result.output, /value = 1;\n {2}enabled/);
|
||||
assert.match(result.output, /string;\nfunction overloaded/);
|
||||
assert.equal(syntax(result.output), syntax(source));
|
||||
const [repeated] = await eslint.lintText(result.output, {
|
||||
filePath: "apps/web/src/spacing-example.tsx",
|
||||
});
|
||||
|
||||
assert.equal(repeated?.errorCount, 0);
|
||||
assert.equal(repeated?.output, undefined);
|
||||
});
|
||||
|
||||
test("la biblioteca de terceros queda excluida", async () => {
|
||||
assert.equal(await eslint.isPathIgnored("packages/shadcn-ui/src/components/button.tsx"), true);
|
||||
});
|
||||
|
||||
test("Biome y ESLint conservan el espaciado al repetir el formateo", async () => {
|
||||
const format = (text) =>
|
||||
execFileSync(
|
||||
"pnpm",
|
||||
["exec", "biome", "format", "--stdin-file-path=apps/web/src/spacing-example.tsx"],
|
||||
{ input: text, encoding: "utf8" }
|
||||
);
|
||||
|
||||
const [spaced] = await eslint.lintText(format(source), {
|
||||
filePath: "apps/web/src/spacing-example.tsx",
|
||||
});
|
||||
|
||||
assert.ok(spaced);
|
||||
assert.equal(spaced.errorCount, 0);
|
||||
const output = spaced.output ?? format(source);
|
||||
const biomeOutput = format(output);
|
||||
const [repeated] = await eslint.lintText(biomeOutput, {
|
||||
filePath: "apps/web/src/spacing-example.tsx",
|
||||
});
|
||||
|
||||
assert.equal(repeated?.errorCount, 0);
|
||||
assert.equal(repeated?.output, undefined);
|
||||
assert.equal(syntax(output), syntax(biomeOutput));
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user