Skip to content

Conversation

@codedgar
Copy link

@codedgar codedgar commented Jan 19, 2026

EsCSS: CSS con sintaxis en Español 🎨

Descripción

Este PR introduce EsCSS, un nuevo añadido al proyecto que permite escribir CSS usando propiedades, valores y selectores en español. Sigue los mismos patrones establecidos por EsJS, EsHTML y EsVue.

Ejemplo

/* EsCSS (entrada) */
cuerpo {
  fuente-familia: sistema-ui, sans-serif;
  fondo-color: humo-blanco;
  color: gris-tenue;
}

.tarjeta {
  mostrar: flex;
  flex-direccion: columna;
  relleno: 1.5rem;
  borde-radio: 0.5rem;
  caja-sombra: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.boton:encima {
  fondo-color: azul-medianoche;
}

@medios (maximo-ancho: 768px) {
  .contenedor {
    relleno: 1rem;
  }
}
/* CSS (salida) */
body {
  font-family: system-ui, sans-serif;
  background-color: whitesmoke;
  color: dimgrey;
}

.tarjeta {
  display: flex;
  flex-direction: column;
  padding: 1.5rem;
  border-radius: 0.5rem;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.boton:hover {
  background-color: midnightblue;
}

@media (max-width: 768px) {
  .contenedor {
    padding: 1rem;
  }
}

¿Qué se agregó?

Nuevos paquetes

Paquete Descripción
@es-js/escss Compilador principal de EsCSS ↔ CSS
@es-js/escss-generator Scripts para generar los diccionarios de traducción
@es-js/vite-plugin-escss Plugin de Vite para archivos .escss
@es-js/prettier-plugin-escss Plugin de Prettier para formatear archivos .escss

Actualizaciones a paquetes existentes

Paquete Cambios
@es-js/language-tools Soporte de sintaxis para .escss (highlighting)
@es-js/vscode-esjs Registro del lenguaje .escss en VSCode
@es-js/esvue Soporte para bloques <estilo> con EsCSS

Nueva demo

  • demos/demo-escss/ - Proyecto de ejemplo con Vite

¿Cómo funciona?

Arquitectura

┌─────────────────────┐
│  escss-generator    │  ← Genera diccionarios de traducción
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│      escss          │  ← Compilador principal (usa PostCSS)
│  ├── properties.ts  │     - 300+ propiedades
│  ├── values.ts      │     - 370+ valores (incluye 140 colores)
│  ├── at-rules.ts    │     - @medios, @fotogramas, etc.
│  ├── pseudo.ts      │     - :encima, ::antes, etc.
│  └── selectors.ts   │     - cuerpo, boton, etc.
└──────────┬──────────┘
           │
           ▼
┌─────────────────────────────────────────────┐
│  Herramientas                               │
│  ├── vite-plugin-escss   (compilación)      │
│  ├── prettier-plugin-escss (formateo)       │
│  └── language-tools      (syntax highlight) │
└─────────────────────────────────────────────┘

Sistema de traducción

El generador usa un diccionario basado en palabras que traduce fragmentos de propiedades CSS:

// Fragmentos de palabras
'background'  'fondo'
'color'  'color'
'border'  'borde'
'radius'  'radio'

// Se combinan automáticamente
'background-color'  'fondo-color'
'border-radius'  'borde-radio'
'border-top-color'  'borde-arriba-color'

Compilación bidireccional

import { compile } from '@es-js/escss'

// EsCSS → CSS
const css = compile(escssCode, { from: 'escss', to: 'css' })

// CSS → EsCSS
const escss = compile(cssCode, { from: 'css', to: 'escss' })

¿Cómo probarlo?

1. Instalar dependencias

pnpm install

2. Construir los paquetes

pnpm build

3. Ejecutar la demo

cd demos/demo-escss
pnpm dev

Abrir http://localhost:5173 en el navegador.

4. Ejecutar tests

# Tests del compilador
cd packages/escss
pnpm test:run

# Tests de language-tools
cd packages/language-tools
pnpm test:run

Uso en un proyecto

Instalación

pnpm add @es-js/escss @es-js/vite-plugin-escss

Configuración de Vite

// vite.config.ts
import { defineConfig } from 'vite'
import EsCSS from '@es-js/vite-plugin-escss'

export default defineConfig({
  plugins: [EsCSS()]
})

Crear archivo .escss

/* estilos.escss */
.tarjeta {
  mostrar: flex;
  flex-direccion: columna;
  fondo-color: blanco;
  relleno: 1rem;
  borde-radio: 0.5rem;
}

.tarjeta:encima {
  caja-sombra: 0 4px 12px rgba(0, 0, 0, 0.15);
}

Importar en HTML o JS

<!-- En HTML -->
<link rel="stylesheet" href="./estilos.escss">
// En JavaScript
import './estilos.escss'

Características soportadas

Propiedades (300+)

  • Layout: mostrar, posicion, flex-direccion, cuadricula-plantilla-columnas
  • Box Model: margen, relleno, borde, ancho, alto
  • Tipografía: fuente-familia, fuente-tamanio, texto-alinear
  • Colores: color, fondo-color, borde-color
  • Y muchas más...

Valores (370+)

  • Display: ninguno, bloque, flex, cuadricula
  • Posición: relativo, absoluto, fijo, pegajoso
  • Flexbox: fila, columna, centro, espacio-entre
  • Colores: rojo, azul, verde-oscuro, azul-cielo-claro (140 colores)
  • Y muchos más...

At-rules

EsCSS CSS
@medios @media
@fotogramas @keyframes
@importar @import
@fuente-cara @font-face
@soporta @supports

Pseudo-clases

EsCSS CSS
:encima :hover
:activo :active
:enfoque :focus
:primer-hijo :first-child
:hijo-n() :nth-child()

Pseudo-elementos

EsCSS CSS
::antes ::before
::despues ::after
::primera-linea ::first-line
::seleccion ::selection

Selectores de elementos

EsCSS CSS
cuerpo body
boton button
entrada input
formulario form
encabezado header

Exports del paquete @es-js/escss

// Import principal
import { compile } from '@es-js/escss'

// Utilidades
import { transformSelector, transformValue } from '@es-js/escss/utils'

// Diccionarios (para uso avanzado)
import {
  getPropertiesDictionary,
  getValuesDictionary,
  getAtRulesDictionary,
  getSelectorsDictionary,
  getPseudoClassesDictionary,
  getPseudoElementsDictionary,
} from '@es-js/escss/dictionaries'

Checklist

  • Compilador EsCSS ↔ CSS
  • Plugin de Vite
  • Plugin de Prettier
  • Syntax highlighting (language-tools)
  • Integración con EsVue (<estilo>)
  • Demo funcional
  • Tests (18 tests pasando)
  • Documentación

Próximos pasos (fuera de este PR)

  • Soporte para source maps
  • Hot Module Replacement (HMR) mejorado
  • Traducción de funciones CSS (repetir(), trasladarY())
  • Extensión de VSCode dedicada con autocompletado

Summary by CodeRabbit

  • Nuevas Características

    • Se añade EsCSS como paquete compilador (bi-direccional), utilidades y generadores de diccionarios.
    • Plugin de Vite para servir/transformar .escss y plugin de Prettier para formateo EsCSS.
    • Integración en VSCode y herramientas de lenguaje (gramática y configuración).
    • Demo y ejemplos (demo EsCSS) incluidos.
  • Tests

    • Suite de pruebas ampliada para compilación EsCSS↔CSS.
  • Chores

    • Nuevas configuraciones de paquetes y build para distribuir los módulos.

@vercel
Copy link

vercel bot commented Jan 19, 2026

@codedgar is attempting to deploy a commit to the OSS Program EsJS Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 19, 2026

Walkthrough

Se añade un ecosistema EsCSS: demo y Vite config, generador de diccionarios, compilador bidireccional EsCSS↔CSS, plugins (Vite, Prettier), herramientas de lenguaje (TextMate/config), integración en esvue, tests y paquetes de soporte TypeScript/Build.

Changes

Cohort / File(s) Summary
Demo EsCSS
demos/demo-escss/estilos.escss, demos/demo-escss/index.html, demos/demo-escss/package.json, demos/demo-escss/vite.config.js
Nueva demo estática en EsCSS con estilos, HTML y configuración de Vite y scripts npm.
Generador EsCSS
packages/escss-generator/package.json, packages/escss-generator/tsconfig.json, packages/escss-generator/src/index.ts, packages/escss-generator/src/word-dictionary.ts, packages/escss-generator/src/generate-*.ts
Nuevo paquete que genera código TypeScript para diccionarios: propiedades, valores, at-rules, selectores y pseudo (incluye utilidades de inversión y generación de código).
Paquete principal EsCSS
packages/escss/package.json, packages/escss/build.config.ts, packages/escss/tsconfig.json, packages/escss/vite.config.ts, packages/escss/src/...
Biblioteca de compilación bidireccional con PostCSS, funciones transformadoras (selectores, valores, media params), reexports de diccionarios y tests de compilación.
Utilities
packages/escss/src/utils/transformSelector.ts, packages/escss/src/utils/transformValue.ts, packages/escss/src/utils.ts, packages/escss/src/dictionaries.ts
Nuevas utilidades para transformar selectores y valores; módulo agregador y barrel que expone getters de diccionarios generados.
Plugin Prettier
packages/prettier-plugin-escss/package.json, packages/prettier-plugin-escss/tsconfig.json, packages/prettier-plugin-escss/vite.config.ts, packages/prettier-plugin-escss/src/index.ts
Plugin que integra EsCSS en Prettier: parser/preprocess convierte EsCSS→CSS, printer formatea CSS y reconvierte a EsCSS.
Plugin Vite
packages/vite-plugin-escss/package.json, packages/vite-plugin-escss/build.config.ts, packages/vite-plugin-escss/tsconfig.json, packages/vite-plugin-escss/src/index.ts
Plugin Vite que sirve y transforma .escss en dev server y en transform hooks, con opción de habilitar/deshabilitar.
Herramientas de lenguaje
packages/language-tools/package.json, packages/language-tools/assets/css-language-configuration.json, packages/language-tools/src/generate-escss-syntax.js, packages/language-tools/src/generate-escss-language-configurations.js, packages/language-tools/src/index.js
Scripts que generan TextMate grammar y configuración de lenguaje para EsCSS; añade assets y export entries.
VSCode
packages/vscode-esjs/package.json, packages/vscode-esjs/scripts/generate-grammars-configurations.ts
Contribución de lenguaje EsCSS a la extensión VSCode (activación, gramática y configuración).
Integración esvue
packages/esvue/package.json, packages/esvue/build.config.ts, packages/esvue/src/index.ts
Soporte para compilar bloques <style> EsCSS en componentes esvue; añade dependencia y marca @es-js/escss como external en build config.
Packaging / Build configs
packages/escss-generator/*, packages/escss/*, packages/vite-plugin-escss/*, packages/prettier-plugin-escss/*
Nuevos package.json, tsconfig y build.config para paquetes agregados; exports y tipos configurados para ESM/CJS y declaraciones.
Tests
packages/escss/test/compile.test.ts
Suite de pruebas extensa que valida transformaciones bidireccionales, pseudo, at-rules, media queries, valores y casos borde.

Esfuerzo estimado de revisión de código

🎯 4 (Complejo) | ⏱️ ~60 minutos

Poema

🐰 Soy un conejito que traduce estilos,
con saltos de "ancho" a "width" y destellos,
entre plugins y demos me pongo a bailar,
convierto EsCSS en CSS y vuelvo a transformar.
¡Brinca el código, que vamos a liberar! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 65.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed El título es específico y describe claramente el cambio principal: la adición completa de soporte para EsCSS, un compilador bidireccional y ecosistema para CSS en español.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 24

🤖 Fix all issues with AI agents
In `@demos/demo-escss/package.json`:
- Around line 1-26: El package.json no declara el tipo de módulo y Vite está
usando ESM en vite.config.js, lo que provoca errores al Node interpretar .js;
fix: add "type": "module" at the top-level of package.json (next to
name/version) so Node treats .js files as ESM, or alternatively rename
vite.config.js to vite.config.mjs; update package.json (the file shown)
accordingly and ensure any tooling or import statements in vite.config.js remain
unchanged.

In `@packages/escss-generator/src/generate-at-rules.ts`:
- Around line 23-68: The generated getDictionary currently calls
invertMap(dictionary) on every inverted request; modify the generator
(generateAtRulesCode) so the emitted module stores a lazily-initialized cached
Map (e.g., a module-scoped variable like invertedDictionary: Map<string,string>
| undefined) and in getDictionary(inverted = false) return dictionary when false
and if inverted return the cached invertedDictionary if set or compute
invertMap(dictionary), assign it to invertedDictionary, and then return it;
reference the existing symbols dictionary, getDictionary, invertMap and
generateAtRulesCode when making this change.

In `@packages/escss-generator/src/generate-properties.ts`:
- Around line 322-356: The generated properties code currently lets duplicate
EsCSS names overwrite entries and breaks bijectivity; in
generatePropertiesCode() (which uses generatePropertiesMapping() and
translateProperty()), validate the mappings before building the dictionary by
scanning the generated [css, escss] pairs for duplicate escss keys, collect
conflicts (escss -> list of css names) and throw a clear error (or fail the
build) listing the duplicated escss names and their original CSS properties so
the author can resolve collisions; ensure this validation runs prior to
constructing the entries and that getDictionary()/dictionary remain unchanged
once the check passes.

In `@packages/escss-generator/src/generate-values.ts`:
- Around line 196-200: Hay una entrada duplicada para la clave 'both' en la
lista que construye el Map (aparece en "Animation values" y otra vez en "Resize
values" como ['both','ambos']); elimina la segunda aparición en el bloque
"Resize values" (la tupla ['both','ambos']) para evitar confusión y dejar una
única definición, asegurándote de actualizar cualquier comentario cercano si es
necesario y conservar la consistencia con las claves usadas por la función que
crea el Map en generate-values.ts.
- Around line 412-428: The generated transformValue implementation (function
transformValue) incorrectly uses value.split(/\s+/) which breaks CSS functions
(e.g., calc(), rgb(), url()) and results in dead/incorrect generated code; fix
the generator to either emit the correct implementation that preserves function
contents by tokenizing/splitting on whitespace while tracking parenthesis depth
(mirroring the working logic in packages/escss/src/utils/transformValue.ts) or
stop generating this function and import/reuse the existing transformValue
implementation; ensure no leftover dead code remains in the generated output and
update references to the symbol transformValue accordingly.

In `@packages/escss-generator/src/index.ts`:
- Around line 1-54: This module currently runs generate() during import causing
side effects; remove the top-level generate() invocation and instead export the
generate function so callers can invoke it, and add a guarded runner that only
calls generate() when the file is executed as the entrypoint (use
fileURLToPath(import.meta.url) vs process.argv[1] check). Keep ensureDir,
outputDir and all generator imports unchanged; ensure no other top-level file
writes occur so importing this module no longer writes files automatically.

In `@packages/escss/package.json`:
- Around line 24-32: Mover "@es-js/core" de devDependencies a dependencies en
package.json porque las exportaciones reexportadas desde index.ts (módulos
properties, values, at-rules, pseudo, selectors) usan invertMap de
`@es-js/core/utils` en las funciones runtime getDictionary(),
getPseudoClassesDictionary() y getPseudoElementsDictionary(); además
build.config.ts lo marca como external (no se bundlea), por lo que los
consumidores deben recibirlo al instalar escss. Actualiza package.json para
colocar "@es-js/core" bajo "dependencies" y publica/rebuild para que las
dependencias runtime estén disponibles para los consumidores.

In `@packages/escss/src/index.ts`:
- Around line 41-56: El problema: la función compile está mutando el objeto
options recibido; evita mutar argumentos de entrada. Solución: en la función
compile crea un nuevo objeto local (por ejemplo const opts = { from: 'escss',
to: 'css', ...options } o usando Object.assign) y usa ese objeto (opts) para las
comprobaciones y retorno en lugar de modificar options; actualiza las
referencias a options.from/options.to para usar opts.from/opts.to y mantiene la
firma y el tipo CompileOptions sin mutar el parámetro original.

In `@packages/escss/src/utils/transformSelector.ts`:
- Around line 20-29: The current regex in transformSelector.ts used in the
result.replace call includes '[' in the lookbehind, so attribute selectors like
[time] get treated as element names and translated; update the regex to avoid
matching when inside attribute selectors by removing '\[' from the lookbehind
(or add a negative lookbehind for '['), e.g. change
/(?<=^|[\s,>+~\[]|\))\b([a-z][a-z0-9-]*)\b(?![([])/gi to a pattern that does not
allow '[' (for example /(?<=^|[\s,>+~]|\))\b([a-z][a-z0-9-]*)\b(?![([])/gi or
equivalently add (?<!\[) before \b), and keep the replacement logic with
selectorsDict.get(element.toLowerCase()) unchanged.

In `@packages/escss/src/utils/transformValue.ts`:
- Around line 40-69: The splitter splitValuePreservingFunctions currently
ignores quotes and escapes so it wrongly splits on spaces inside quoted strings
(e.g., content: "a  b", font-family: "Open Sans"); update
splitValuePreservingFunctions to track quote state and escape sequences: add
variables for currentQuote (single/double/none) and isEscaped, toggle them when
encountering backslashes or quote chars and only treat a space as a split when
depth === 0 and currentQuote is null (not inside a string); ensure closing
quotes are preserved in current and that escapes inside strings are not consumed
or lost so original literal spacing/escapes remain intact.

In `@packages/escss/vite.config.ts`:
- Around line 1-7: La configuración de Vitest declara globals: true pero los
tests en compile.test.ts importan explícitamente describe, it y expect; cambiad
la configuración para que refleje el patrón actual: en
packages/escss/vite.config.ts actualizad defineConfig -> test -> globals: false;
alternativamente, si preferís usar APIs globales, eliminad los imports de
describe/it/expect en packages/escss/test/compile.test.ts y dejad globals: true.
Aseguraos de mantener la opción coherente con el archivo de test para evitar
redundancia.

In `@packages/esvue/package.json`:
- Around line 20-23: El paquete '@es-js/escss' se está importando en tiempo de
ejecución (ver import { compile as EsCSSCompile } from '@es-js/escss' en
packages/esvue/src/index.ts) pero actualmente figura en devDependencies; mueve
'@es-js/escss' desde devDependencies a dependencies (o a peerDependencies if you
expect consumers to provide it) en packages/esvue/package.json y actualiza la
instalación/lockfile para que los consumidores de `@es-js/esvue` tengan la
dependencia disponible en producción.

In `@packages/language-tools/package.json`:
- Line 27: package.json is missing a "files" field so the generated artifacts
under dist (including escss.tmLanguage.json and
escss-language-configuration.json) won't be included in published packages; add
a "files": ["dist"] entry to package.json and ensure the package.json exports
remain correct, and also verify that the dependency "@es-js/escss" (referenced
from src/generate-escss-syntax.js) is declared in dependencies (not just
devDependencies) since it's required at runtime by source code.

In `@packages/language-tools/src/generate-escss-language-configurations.js`:
- Around line 7-13: The file currently relies on implicit Buffer-to-string
coercion when reading/writing JSON; update the readFileSync and writeFileSync
calls to use explicit UTF-8 encoding to avoid implicit Buffer conversion: call
readFileSync('./assets/css-language-configuration.json', 'utf8') so variable
data is a string before JSON.parse, and call
writeFileSync('dist/escss-language-configuration.json', output, 'utf8') when
writing the output; reference the existing symbols readFileSync, writeFileSync,
data, jsonData, and output when making the change.

In `@packages/language-tools/src/generate-escss-syntax.js`:
- Around line 14-17: The current keyword extraction via getEscssKeywords() can
produce regex patterns with unescaped special characters; update the code that
builds the regex pattern (use the array from getEscssKeywords and the code that
joins them into a pattern) to escape regex metacharacters in each keyword (e.g.,
replace characters like . * + ? ^ $ { } ( ) | [ ] \ / with escaped versions)
before joining, so the generated pattern is always valid and matches the literal
keywords; locate and modify the pattern construction that consumes
getEscssKeywords() and apply an escape function to each keyword.

In `@packages/prettier-plugin-escss/package.json`:
- Around line 26-35: The package.json currently lists "@es-js/escss" under
"devDependencies" but it is required at runtime by the Prettier plugin; move the
entry "@es-js/escss": "workspace:*" from the "devDependencies" object into the
top-level "dependencies" object (preserving the version specifier) so the
runtime bundle/install will include it; update any package.json
ordering/formatting as needed.
- Line 2: Actualizar el campo "name" en package.json que actualmente es
"prettier-plugin-escss" para usar el scope del monorepo, cambiándolo a
"@es-js/prettier-plugin-escss"; busca el valor "prettier-plugin-escss" en
package.json del paquete y reemplázalo por "@es-js/prettier-plugin-escss", y
luego verifica que cualquier referencia a ese nombre (por ejemplo en la
configuración de publicación o en dependencias internas) se actualice en caso de
ser necesario.

In `@packages/prettier-plugin-escss/src/index.ts`:
- Around line 61-67: El acceso a la API interna de Prettier mediante
Symbol.for('comments') es frágil y puede romperse en futuras versiones; update
el bloque donde se lee options[Symbol.for('comments')] (la variable comments)
para añadir un comentario claro explicando que esto usa una API interna no
documentada de Prettier, por qué se hizo (compatibilidad con otros plugins), los
riesgos asociados y que debe ser revisado/monitoreado en upgrades de Prettier
(añadir un TODO con referencia a versión/issue tracking si procede).
- Around line 69-81: El printer actualmente toma options.originalText (EsCSS) y
lo pasa a format(parser: 'css'), lo cual es incorrecto; en lugar de usar
options.originalText en la llamada a format() dentro de la función que hace el
formateo, utiliza el CSS compilado que devuelve preprocess (la variable que
contiene el CSS generado por EsCSSCompile/preprocess) como entrada a format(), y
luego convierte el resultado formateado de nuevo a EsCSS con
EsCSSCompile(formatted, { from: 'css', to: 'escss' }); ajusta las referencias en
la función del printer para reemplazar options.originalText por la variable del
CSS compilado (no cambiar EsCSSCompile ni format() firmas).

In `@packages/prettier-plugin-escss/vite.config.ts`:
- Around line 1-7: Remove the unnecessary globals setting from the Vitest
configuration: update the object passed to defineConfig by removing the globals:
true property inside the test configuration (the test key may remain if you plan
to add tests later), so eliminate the unused globals flag referenced as globals
in the test object to avoid enabling Vitest globals when no tests/imports are
present.

In `@packages/vite-plugin-escss/package.json`:
- Around line 22-30: Mover "vite" de "dependencies" a "peerDependencies" para
indicar que el plugin debe usar la instancia de Vite del consumidor, y mover
"@es-js/escss" de "devDependencies" a "dependencies" porque su función compile
se utiliza en tiempo de ejecución dentro de los hooks configureServer y
transform; actualiza package.json para reflejar estos cambios y asegúrate de
ajustar semver si es necesario (por ejemplo mantener "^5.0.0" para vite en
peerDependencies y "workspace:*" o la versión adecuada para `@es-js/escss` en
dependencies) para evitar romper la resolución de paquetes.

In `@packages/vite-plugin-escss/src/index.ts`:
- Around line 64-68: El middleware usa readFileSync(filePath, 'utf-8') que
bloquea el event loop; reemplázalo por la API asíncrona (fs/promises) para leer
el archivo sin bloquear: importar readFile desde 'fs/promises', await
readFile(filePath, 'utf-8') dentro del try/catch del handler que llama a
compile(raw, { from: 'escss', to: 'css' }) y luego usar
res.setHeader('Content-Type', 'text/css') y res.end(compiled) exactamente como
ahora; asegúrate de marcar la función que contiene este código como async y de
manejar errores correctamente en el catch.
- Around line 77-81: The transform hook currently returns compiled CSS but omits
moduleType, so update the transform(raw: string, id: string) function to return
an object including moduleType: 'css' (e.g., return { code: compiled, map: null,
moduleType: 'css' }) so Vite treats .escss imports as CSS modules and enables
CSS pipeline/HMR when importing .escss from JS/TS; keep the existing compiled
value and map handling.

In `@packages/vscode-esjs/scripts/generate-grammars-configurations.ts`:
- Line 6: The grammars list (const grammars) includes escss.tmLanguage.json but
`@es-js/language-tools` does not publish escss.tmLanguage.json or
escss-language-configuration.json; either publish those two files in the
`@es-js/language-tools` package build so the script can download them, or
remove/guard the escss entries from the grammars and any corresponding
configuration lists (escss.tmLanguage.json and
escss-language-configuration.json) and add error handling to skip missing files
when fetching from unpkg.com; update the grammars array (and any configuration
arrays) or the package build accordingly to resolve the axios download failures.

@vercel
Copy link

vercel bot commented Jan 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
esjs Ready Ready Preview, Comment Feb 9, 2026 0:00am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
editor Ignored Ignored Preview Feb 9, 2026 0:00am

Request Review

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

🤖 Fix all issues with AI agents
In `@packages/escss-generator/src/generate-properties.ts`:
- Around line 345-393: La plantilla generada exporta el identificador export
const cssProperties, que puede colisionar con el mismo nombre en consumidores;
en generatePropertiesCode cambia la línea que emite "export const cssProperties
= [" por un nombre no conflictivo (por ejemplo "export const allCssProperties" o
similar) y actualiza cualquier referencia interna del archivo generado (y la
documentación en el mismo template) para usar ese nuevo identificador; asegúrate
además de actualizar donde sea consumido fuera del generador para importar el
nuevo nombre o mantener un re-export explícito si se requiere compatibilidad.

In `@packages/escss-generator/src/generate-pseudo.ts`:
- Around line 107-204: generatePseudoCode currently injects raw strings into the
template (e.g., in classEntries, elementEntries and the various list mappings
like cssPseudoClassesList, escssPseudoClassesList, cssPseudoElementsList,
escssPseudoElementsList) using `'${escss}'` / `'${css}'` which breaks if values
contain single quotes or backslashes; fix by escaping values before
interpolation (create/inline an escape function used when building classEntries
and elementEntries and when mapping keys/values for the lists — e.g., replace
backslashes and single quotes with escaped versions or use JSON.stringify and
strip its surrounding quotes) so every injected string is safely escaped in
generatePseudoCode and all occurrences of direct `'${...}'` interpolation are
replaced with the escaped variant.

In `@packages/escss-generator/src/generate-selectors.ts`:
- Line 77: La entrada ['source', 'fuente'] en generate-selectors.ts puede
confundirse con la traducción de la propiedad font en word-dictionary.ts; cambia
la segunda elemento de ese par a 'origen' (o a otro término no ambigua como
'origen-source') para desambiguar el selector <source> de la fuente tipográfica,
actualiza cualquier prueba o comentario que asuma 'fuente' y mantén consistencia
con la traducción 'source' → 'origen' usada en word-dictionary.ts.
- Around line 5-96: The cssElementTranslations map in generate-selectors.ts
lacks duplicate-target detection like generatePropertiesMapping in
generate-properties.ts, so add collision validation in generateSelectorsCode:
iterate cssElementTranslations (or during codegen) and build a reverse map/set
of target names (the EsCSS names), and if a target is already present throw or
surface a clear error (including the conflicting source keys and the target) to
prevent two CSS keys mapping to the same EsCSS name; mirror the validation
behavior and error format used by generatePropertiesMapping so future additions
are caught consistently.

In `@packages/escss-generator/src/generate-values.ts`:
- Around line 374-376: The array contains duplicate mappings ['currentcolor',
'color-actual'] and ['currentColor', 'color-actual'] which are redundant because
the generated Map for the EsCSS→CSS direction will only keep the last entry
(resulting in 'color-actual' → 'currentColor'); remove the lowercase
'currentcolor' entry or add an inline comment next to the ['currentcolor',
'color-actual'] element explaining that the duplicate is intentional to show
canonical casing and that the Map will normalize to 'currentColor'.
- Around line 229-230: Las entradas de alias ['gray','gris'] y ['grey','gris']
producen claves duplicadas en el Map y la última definición vence, imponiendo la
grafía británica; arregla esto haciendo explícita la canonización: en el array
de pares de alias (p. ej. donde están ['gray','gris'], ['grey','gris'] y
similares) elimina uno de los alias o reemplaza ambos pares por un único par que
use la grafía preferida (por ejemplo ['gray','gris'] si quieres emitir "gray"),
o bien deduplica/normaliza antes de construir el Map (convertir ambos alias a la
misma clave canónica). Aplica el mismo cambio a las otras ocurrencias señaladas
(líneas similares con dark/light/slate/dim) para evitar que la última entrada
del Map sobrescriba la grafía deseada.

In `@packages/escss-generator/src/index.ts`:
- Around line 54-56: La guardia de entry-point usando direct comparación de
process.argv[1] con fileURLToPath(import.meta.url) es frágil; en la
función/archivo que contiene generate(), sustituye esa comparación por una
comparación de rutas resueltas usando fs.realpathSync para normalizar ambos
extremos (resolver realpath de fileURLToPath(import.meta.url) y, si existe,
realpathSync(process.argv[1])) y sólo invoca generate() cuando ambas rutas
resueltas coincidan; además defensivamente comprueba que process.argv[1] existe
antes de llamar a realpathSync para evitar errores en entornos sin ese índice.
- Around line 21-52: The generate() function calls writeFileSync multiple times
(for properties.ts, values.ts, at-rules.ts, pseudo.ts, selectors.ts) without
error handling; wrap the sequence of file writes in a try/catch (or implement an
atomic/temp-file + rename strategy) so failures don’t leave outputDir in a
partially-written state: capture exceptions around the calls to writeFileSync,
log a clear error via console.error (including the filename and the caught
error), and if using a multi-step approach, ensure cleanup/rollback of any temp
files; reference generate(), writeFileSync, outputDir and the helpers
generatePropertiesCode(), generateValuesCode(), generateAtRulesCode(),
generatePseudoCode(), generateSelectorsCode() when locating the changes.

In `@packages/escss-generator/src/word-dictionary.ts`:
- Line 179: Remove the duplicate ['stretch', 'estirar'] tuple from the list of
word pairs in packages/escss-generator/src/word-dictionary.ts so the pair only
appears once; locate the array of translation pairs used to build the Map (the
list of ['english','spanish'] entries that feed the Map construction) and delete
the second occurrence (the one at line ~179) to avoid unnecessary duplication
while leaving the original entry (line ~60) intact.
- Around line 5-230: The issue: cssWordTranslations contains duplicate target
values (e.g., "espacio", "relleno", "origen") so createInvertedWordDictionary()
silently overwrites keys when inverting; fix by changing
createInvertedWordDictionary() to accumulate an array of source keys per
translated value (Map<string, string[]>), emit a warning when collisions are
detected (use console.warn or processLogger), update any callers expecting the
old Map<string,string> to handle arrays, and remove the duplicate
['stretch','estirar'] entry from cssWordTranslations; keep function name
createInvertedWordDictionary and map cssWordTranslations as the referenced
symbols.

In `@packages/escss/src/index.ts`:
- Around line 91-94: The catch block currently logs the error as console.error({
error }), which wraps the Error in an object and hides the stack; replace that
call so the error is logged directly with context (e.g., a descriptive string
plus the error) in the catch that returns content — locate the catch where
console.error({ error }) is used in packages/escss/src/index.ts and change it to
log the message and the error variable itself (preserving the stack) before
returning content.

In `@packages/escss/src/utils/transformSelector.ts`:
- Around line 43-59: The current replace call on result (the result.replace(...)
that matches /:([a-zñ-]+)(?:\(([^)]*)\))?/gi) can wrongly match the second colon
of a pseudo-element already transformed; change the match logic so pseudo-class
matches are not preceded by another colon (e.g. use a negative lookbehind like
(?<!:):([a-zñ-]+)(?:\(([^)]*)\))? or a preceding-char check inside the replace
callback) before consulting pseudoClassDict; update the replace invocation in
transformSelector (the result.replace block) to use that regex or check so
pseudo-elements (double-colon) are excluded reliably.
- Around line 20-29: La expresión regular usada en la llamada a result.replace
en transformSelector.ts no transforma nombres de elemento seguidos por '[' (p.
ej. div[class]) porque el lookahead negativo contiene '['; elimina el carácter
'[' del conjunto dentro del lookahead negativo (la parte (?![([\]=])) para que
pase a excluir sólo los demás caracteres pero no '['), dejando el lookbehind
(?<!\[) intacto; busca la llamada que usa result.replace con la regex
/(?<=^|[\s,>+~]|\))(?<!\[)\b([a-zñ][a-zñ0-9-]*)\b(?![([\]=])/gi y actualiza solo
el lookahead negativo para permitir transformar elementos antes de '[' mientras
que sigue evitando coincidencias dentro de corchetes (variables relevantes:
result, selectorsDict).

In `@packages/escss/test/compile.test.ts`:
- Around line 203-250: The CSS→EsCSS suite ("describe('CSS to EsCSS')") lacks
coverage compared to the reverse direction; add tests calling compile(input, {
from: 'css', to: 'escss' }) that assert translations for missing cases: semantic
value mappings (e.g., block→bloque and common color names/hex), `@keyframes`
rules, pseudo-elements (::before/::after → their EsCSS tokens), border
shorthand/individual border properties, and grid/flex properties (e.g., display:
grid/flex, grid-template, justify-content). For each case add a focused it(...)
using the same pattern as existing tests and assert with
expect(output).toContain(...) referencing the expected EsCSS tokens so
bidirectional parity is validated.
- Around line 34-43: La prueba "should transform background properties" es
insuficiente porque solo verifica la presencia de 'background-color'; actualiza
el test que llama a compile(input, { from: 'escss', to: 'css' }) para verificar
además que ambos mapeos de EsCSS ('color-fondo' y 'fondo-color') aparecen como
'background-color' con los valores traducidos ('azul' → 'blue' y 'rojo' →
'red'); es decir, comprueba que la salida contenga "background-color: blue" y
"background-color: red" (o las cadenas equivalentes en la salida) para asegurar
que ambos nombres de propiedad y ambos valores se transformaron correctamente.
- Around line 252-303: Add a bidirectional round-trip test that uses the
compile(...) helper to convert EsCSS→CSS and then compile the resulting CSS back
to EsCSS (and a separate case CSS→EsCSS→CSS), asserting the final string equals
the original (or a normalized form e.g., trimmed/consistent whitespace). Locate
tests near the existing "Edge cases" block and add two it(...) cases referencing
the compile function: one that starts with an EsCSS input, compiles to CSS and
back to EsCSS and asserts equality, and one that starts with CSS and round-trips
to CSS; ensure any normalization used for comparison is applied consistently to
both original and round-tripped outputs.
- Around line 158-174: The test is asserting an invalid CSS value: change the
expectation in compile.test.ts from 'empty-cells: hidden' to 'empty-cells:
hide'; then update the translation dictionary (the value mapping that currently
maps 'oculto' -> 'hidden') to include a 'hide' target and add a
property-specific rule so transformValue() can return 'hide' when the property
is 'empty-cells' (leave general mapping for other properties as 'hidden' if
needed); locate and modify transformValue() to accept property context (or add a
small conditional branch keyed on the property name) so the compile(input, ...)
path produces 'empty-cells: hide'.

Comment on lines +345 to +393
export function generatePropertiesCode(): string {
const mappings = generatePropertiesMapping()

const entries = mappings
.map(([css, escss]) => ` ['${escss}', '${css}'],`)
.join('\n')

return `import { invertMap } from '@es-js/core/utils'

/**
* EsCSS to CSS property dictionary.
* Maps Spanish CSS property names to standard CSS property names.
* Auto-generated by escss-generator.
*/
const dictionary: Map<string, string> = new Map([
${entries}
])

/**
* Get the properties dictionary.
* @param inverted - If true, returns CSS -> EsCSS mapping, otherwise EsCSS -> CSS
*/
let invertedDictionary: Map<string, string> | undefined

export function getDictionary(inverted = false): Map<string, string> {
if (!inverted) {
return dictionary
}
if (!invertedDictionary) {
invertedDictionary = invertMap(dictionary)
}
return invertedDictionary
}

/**
* List of all CSS properties.
*/
export const cssProperties = [
${cssProperties.map((p) => ` '${p}',`).join('\n')}
]

/**
* List of all EsCSS properties.
*/
export const escssProperties = [
${mappings.map(([, escss]) => ` '${escss}',`).join('\n')}
]
`
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

El código generado re-exporta cssProperties con el mismo identificador que el source.

El template genera export const cssProperties = [...] (línea 382) en el archivo de salida. Esto funciona porque el archivo generado y el archivo fuente son módulos distintos. Sin embargo, si algún consumidor importa ambos módulos, podría haber confusión de nombres. Considerar renombrar en el archivo generado (p. ej. allCssProperties) o documentar explícitamente que la fuente canónica es el archivo generado.

🤖 Prompt for AI Agents
In `@packages/escss-generator/src/generate-properties.ts` around lines 345 - 393,
La plantilla generada exporta el identificador export const cssProperties, que
puede colisionar con el mismo nombre en consumidores; en generatePropertiesCode
cambia la línea que emite "export const cssProperties = [" por un nombre no
conflictivo (por ejemplo "export const allCssProperties" o similar) y actualiza
cualquier referencia interna del archivo generado (y la documentación en el
mismo template) para usar ese nuevo identificador; asegúrate además de
actualizar donde sea consumido fuera del generador para importar el nuevo nombre
o mantener un re-export explícito si se requiere compatibilidad.

Comment on lines +107 to +204
export function generatePseudoCode(): string {
const classEntries = Array.from(cssPseudoClasses.entries())
.map(([css, escss]) => ` ['${escss}', '${css}'],`)
.join('\n')

const elementEntries = Array.from(cssPseudoElements.entries())
.map(([css, escss]) => ` ['${escss}', '${css}'],`)
.join('\n')

return `import { invertMap } from '@es-js/core/utils'

/**
* EsCSS to CSS pseudo-classes dictionary.
* Maps Spanish pseudo-class names to standard CSS pseudo-class names.
* Auto-generated by escss-generator.
*/
const pseudoClassesDictionary: Map<string, string> = new Map([
${classEntries}
])

/**
* EsCSS to CSS pseudo-elements dictionary.
* Maps Spanish pseudo-element names to standard CSS pseudo-element names.
* Auto-generated by escss-generator.
*/
const pseudoElementsDictionary: Map<string, string> = new Map([
${elementEntries}
])

/**
* Get the pseudo-classes dictionary.
* @param inverted - If true, returns CSS -> EsCSS mapping, otherwise EsCSS -> CSS
*/
let invertedPseudoClasses: Map<string, string> | undefined

export function getPseudoClassesDictionary(inverted = false): Map<string, string> {
if (!inverted) {
return pseudoClassesDictionary
}
if (!invertedPseudoClasses) {
invertedPseudoClasses = invertMap(pseudoClassesDictionary)
}
return invertedPseudoClasses
}

/**
* Get the pseudo-elements dictionary.
* @param inverted - If true, returns CSS -> EsCSS mapping, otherwise EsCSS -> CSS
*/
let invertedPseudoElements: Map<string, string> | undefined

export function getPseudoElementsDictionary(inverted = false): Map<string, string> {
if (!inverted) {
return pseudoElementsDictionary
}
if (!invertedPseudoElements) {
invertedPseudoElements = invertMap(pseudoElementsDictionary)
}
return invertedPseudoElements
}

/**
* List of all CSS pseudo-classes.
*/
export const cssPseudoClassesList = [
${Array.from(cssPseudoClasses.keys())
.map((p) => ` '${p}',`)
.join('\n')}
]

/**
* List of all EsCSS pseudo-classes.
*/
export const escssPseudoClassesList = [
${Array.from(cssPseudoClasses.values())
.map((p) => ` '${p}',`)
.join('\n')}
]

/**
* List of all CSS pseudo-elements.
*/
export const cssPseudoElementsList = [
${Array.from(cssPseudoElements.keys())
.map((p) => ` '${p}',`)
.join('\n')}
]

/**
* List of all EsCSS pseudo-elements.
*/
export const escssPseudoElementsList = [
${Array.from(cssPseudoElements.values())
.map((p) => ` '${p}',`)
.join('\n')}
]
`
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

El código generado es vulnerable a inyección si en el futuro se añaden valores con comillas simples.

Actualmente todos los valores son cadenas simples sin caracteres especiales problemáticos, pero la plantilla usa interpolación directa ('${escss}', '${css}') sin escapar. Si algún valor futuro contiene una comilla simple (e.g., un término con apóstrofe), el código generado se rompería sintácticamente.

♻️ Sugerencia de escapado preventivo
   const classEntries = Array.from(cssPseudoClasses.entries())
-    .map(([css, escss]) => `  ['${escss}', '${css}'],`)
+    .map(([css, escss]) => `  ['${escss.replace(/'/g, "\\'")}', '${css.replace(/'/g, "\\'")}'],`)
     .join('\n')
🤖 Prompt for AI Agents
In `@packages/escss-generator/src/generate-pseudo.ts` around lines 107 - 204,
generatePseudoCode currently injects raw strings into the template (e.g., in
classEntries, elementEntries and the various list mappings like
cssPseudoClassesList, escssPseudoClassesList, cssPseudoElementsList,
escssPseudoElementsList) using `'${escss}'` / `'${css}'` which breaks if values
contain single quotes or backslashes; fix by escaping values before
interpolation (create/inline an escape function used when building classEntries
and elementEntries and when mapping keys/values for the lists — e.g., replace
backslashes and single quotes with escaped versions or use JSON.stringify and
strip its surrounding quotes) so every injected string is safely escaped in
generatePseudoCode and all occurrences of direct `'${...}'` interpolation are
replaced with the escaped variant.

Comment on lines +5 to +96
export const cssElementTranslations = new Map<string, string>([
// Document structure
['html', 'html'],
['body', 'cuerpo'],
['head', 'cabeza'],
['header', 'encabezado'],
['footer', 'pie'],
['main', 'principal'],
['section', 'seccion'],
['article', 'articulo'],
['aside', 'lateral'],
['nav', 'nav'],

// Headings
['h1', 'h1'],
['h2', 'h2'],
['h3', 'h3'],
['h4', 'h4'],
['h5', 'h5'],
['h6', 'h6'],

// Text content
['p', 'p'],
['div', 'div'],
['span', 'span'],
['a', 'a'],
['strong', 'fuerte'],
['em', 'enfasis'],
['br', 'br'],
['hr', 'hr'],
['blockquote', 'cita'],
['pre', 'pre'],
['code', 'codigo'],

// Lists
['ul', 'ul'],
['ol', 'ol'],
['li', 'li'],
['dl', 'dl'],
['dt', 'dt'],
['dd', 'dd'],

// Tables
['table', 'tabla'],
['thead', 'tabla-cabeza'],
['tbody', 'tabla-cuerpo'],
['tfoot', 'tabla-pie'],
['tr', 'tr'],
['th', 'th'],
['td', 'td'],
['caption', 'titulo'],

// Forms
['form', 'formulario'],
['input', 'entrada'],
['button', 'boton'],
['select', 'seleccionar'],
['option', 'opcion'],
['textarea', 'areadetexto'],
['label', 'etiqueta'],
['fieldset', 'conjuntocampos'],
['legend', 'leyenda'],

// Media
['img', 'img'],
['video', 'video'],
['audio', 'audio'],
['canvas', 'lienzo'],
['svg', 'svg'],
['figure', 'figura'],
['figcaption', 'leyendafigura'],
['picture', 'imagen'],
['source', 'fuente'],

// Interactive
['details', 'detalles'],
['summary', 'resumen'],
['dialog', 'dialogo'],

// Embedded
['iframe', 'marco'],
['embed', 'incrustar'],
['object', 'objeto'],

// Other
['address', 'direccion'],
['time', 'tiempo'],
['mark', 'marca'],
['progress', 'progreso'],
['meter', 'medidor'],
['output', 'salida'],
])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Sin detección de colisiones, a diferencia del generador de propiedades.

generatePropertiesMapping() en generate-properties.ts incluye detección de duplicados para evitar que dos claves CSS mapeen al mismo nombre EsCSS. Este generador de selectores no tiene esa validación. Aunque el mapa actual es pequeño y curado manualmente, sería consistente añadirla para detectar colisiones futuras al añadir nuevas entradas.

♻️ Propuesta: añadir validación de colisiones en generateSelectorsCode
 export function generateSelectorsCode(): string {
+  // Validate no duplicate EsCSS names
+  const seen = new Map<string, string>()
+  for (const [css, escss] of cssElementTranslations) {
+    const existing = seen.get(escss)
+    if (existing && existing !== css) {
+      throw new Error(
+        `Colisión en selectores: '${existing}' y '${css}' -> '${escss}'`
+      )
+    }
+    seen.set(escss, css)
+  }
+
   const entries = Array.from(cssElementTranslations.entries())
🤖 Prompt for AI Agents
In `@packages/escss-generator/src/generate-selectors.ts` around lines 5 - 96, The
cssElementTranslations map in generate-selectors.ts lacks duplicate-target
detection like generatePropertiesMapping in generate-properties.ts, so add
collision validation in generateSelectorsCode: iterate cssElementTranslations
(or during codegen) and build a reverse map/set of target names (the EsCSS
names), and if a target is already present throw or surface a clear error
(including the conflicting source keys and the target) to prevent two CSS keys
mapping to the same EsCSS name; mirror the validation behavior and error format
used by generatePropertiesMapping so future additions are caught consistently.

['figure', 'figura'],
['figcaption', 'leyendafigura'],
['picture', 'imagen'],
['source', 'fuente'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Posible ambigüedad: fuente para <source> vs. fuente tipográfica.

El elemento <source> se traduce como fuente, pero en word-dictionary.ts (línea 40) font también se traduce como fuente. Aunque operan en contextos distintos (selectores vs. propiedades), esto podría generar confusión al usuario. Considerar usar origen (coherente con word-dictionary.ts línea 142 donde sourceorigen) u otra alternativa para desambiguar.

🤖 Prompt for AI Agents
In `@packages/escss-generator/src/generate-selectors.ts` at line 77, La entrada
['source', 'fuente'] en generate-selectors.ts puede confundirse con la
traducción de la propiedad font en word-dictionary.ts; cambia la segunda
elemento de ese par a 'origen' (o a otro término no ambigua como
'origen-source') para desambiguar el selector <source> de la fuente tipográfica,
actualiza cualquier prueba o comentario que asuma 'fuente' y mantén consistencia
con la traducción 'source' → 'origen' usada en word-dictionary.ts.

Comment on lines +229 to +230
['gray', 'gris'],
['grey', 'gris'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Alias gray/grey: la inversión siempre selecciona la grafía británica.

Los pares graygris y greygris (y sus variantes dark/light/slate/dim) generan entradas duplicadas ['gris', 'gray'] y ['gris', 'grey'] en el Map. El constructor de Map toma la última entrada, por lo que gris siempre se traduce como grey. Esto puede sorprender a los usuarios que esperan la grafía americana (gray), que es más común en CSS.

Recomiendo hacer explícita la elección: o bien eliminar uno de los alias, o bien documentar cuál grafía se emite al compilar de EsCSS a CSS.

Also applies to: 274-275, 282-283, 293-294, 299-300, 317-318, 363-364

🤖 Prompt for AI Agents
In `@packages/escss-generator/src/generate-values.ts` around lines 229 - 230, Las
entradas de alias ['gray','gris'] y ['grey','gris'] producen claves duplicadas
en el Map y la última definición vence, imponiendo la grafía británica; arregla
esto haciendo explícita la canonización: en el array de pares de alias (p. ej.
donde están ['gray','gris'], ['grey','gris'] y similares) elimina uno de los
alias o reemplaza ambos pares por un único par que use la grafía preferida (por
ejemplo ['gray','gris'] si quieres emitir "gray"), o bien deduplica/normaliza
antes de construir el Map (convertir ambos alias a la misma clave canónica).
Aplica el mismo cambio a las otras ocurrencias señaladas (líneas similares con
dark/light/slate/dim) para evitar que la última entrada del Map sobrescriba la
grafía deseada.

Comment on lines +43 to +59
// Transform pseudo-classes (:hover, :focus, etc.)
// Handle both functional pseudo-classes like :nth-child(n) and simple ones like :hover
result = result.replace(
/:([a-zñ-]+)(?:\(([^)]*)\))?/gi,
(match, pseudoClass, params) => {
// Skip if it's actually a pseudo-element (double colon)
if (match.startsWith('::')) {
return match
}

const transformed = pseudoClassDict.get(pseudoClass.toLowerCase())
if (transformed) {
return params ? `:${transformed}(${params})` : `:${transformed}`
}
return match
}
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

La regex de pseudo-clases puede capturar pseudo-elementos ya transformados.

La regex /:([a-zñ-]+)/ (línea 46) puede hacer match con la segunda parte de ::nombre ya transformado en el paso anterior. Aunque la verificación match.startsWith('::') (línea 49) intenta filtrar esto, el match capturado ya es :nombre (desde el segundo :), por lo que nunca empieza con :: y el filtro no funciona como se espera.

Actualmente no causa un bug visible porque los nombres de pseudo-elementos transformados probablemente no existen en el diccionario de pseudo-clases. Sin embargo, si en el futuro coincidiera algún nombre, habría una doble transformación.

Una solución más robusta sería usar una regex que excluya explícitamente posiciones precedidas por :::

-    /:([a-zñ-]+)(?:\(([^)]*)\))?/gi,
+    /(?<!:):([a-zñ-]+)(?:\(([^)]*)\))?/gi,
🤖 Prompt for AI Agents
In `@packages/escss/src/utils/transformSelector.ts` around lines 43 - 59, The
current replace call on result (the result.replace(...) that matches
/:([a-zñ-]+)(?:\(([^)]*)\))?/gi) can wrongly match the second colon of a
pseudo-element already transformed; change the match logic so pseudo-class
matches are not preceded by another colon (e.g. use a negative lookbehind like
(?<!:):([a-zñ-]+)(?:\(([^)]*)\))? or a preceding-char check inside the replace
callback) before consulting pseudoClassDict; update the replace invocation in
transformSelector (the result.replace block) to use that regex or check so
pseudo-elements (double-colon) are excluded reliably.

Comment on lines +34 to +43
it('should transform background properties', () => {
const input = `.elemento {
color-fondo: azul;
fondo-color: rojo;
}`

const output = compile(input, { from: 'escss', to: 'css' })

expect(output).toContain('background-color')
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

El test de propiedades de fondo es débil: no verifica la traducción de valores.

Se declaran dos propiedades (color-fondo: azul y fondo-color: rojo) pero solo se comprueba que la salida contenga background-color, sin verificar que los valores azulblue y rojored se tradujeron correctamente. Además, no se valida que ambas variantes de la propiedad EsCSS se traduzcan a background-color.

♻️ Corrección propuesta
       const output = compile(input, { from: 'escss', to: 'css' })
 
-      expect(output).toContain('background-color')
+      expect(output).toContain('background-color: blue')
+      expect(output).toContain('background-color: red')
🤖 Prompt for AI Agents
In `@packages/escss/test/compile.test.ts` around lines 34 - 43, La prueba "should
transform background properties" es insuficiente porque solo verifica la
presencia de 'background-color'; actualiza el test que llama a compile(input, {
from: 'escss', to: 'css' }) para verificar además que ambos mapeos de EsCSS
('color-fondo' y 'fondo-color') aparecen como 'background-color' con los valores
traducidos ('azul' → 'blue' y 'rojo' → 'red'); es decir, comprueba que la salida
contenga "background-color: blue" y "background-color: red" (o las cadenas
equivalentes en la salida) para asegurar que ambos nombres de propiedad y ambos
valores se transformaron correctamente.

Comment on lines +158 to +174
it('should transform newly translated properties', () => {
const input = `.elemento {
fuente-variante: normal;
texto-decoracion-grosor: 2px;
transicion-propiedad: todo;
lista-estilo-tipo: disco;
vacio-celdas: oculto;
}`

const output = compile(input, { from: 'escss', to: 'css' })

expect(output).toContain('font-variant: normal')
expect(output).toContain('text-decoration-thickness: 2px')
expect(output).toContain('transition-property: all')
expect(output).toContain('list-style-type: disc')
expect(output).toContain('empty-cells: hidden')
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find packages/escss* -type f -name "*.ts" -o -name "*.js" | head -20

Repository: es-js/esjs

Length of output: 869


🏁 Script executed:

rg "oculto|hide" --type=ts -C2 packages/escss*/src -g '!node_modules/**' | head -100

Repository: es-js/esjs

Length of output: 1088


🏁 Script executed:

rg "empty-cells" --type=ts packages/escss*/test -C3

Repository: es-js/esjs

Length of output: 602


🌐 Web query:

CSS empty-cells property valid values specification

💡 Result:

Per the CSS specifications, empty-cells accepts these keyword values:

  • show | hide (controls whether borders/backgrounds are drawn for visually empty table cells) [1][2]

In CSS 2.1, the property’s value grammar is explicitly:

  • show | hide | inherit (with initial value show, applies to table-cell elements, inherited: yes) [1]

In modern CSS (CSS Tables Module Level 3), the formal syntax is show | hide, and (as with other properties) CSS-wide/global keywords like inherit, initial, unset, revert, revert-layer are also allowed [2].

Sources: [1] CSS2.1 spec section on empty-cells (17.6.1.1); [2] MDN empty-cells reference.


🏁 Script executed:

rg "hide" --type=ts packages/escss*/src -C2

Repository: es-js/esjs

Length of output: 36


🏁 Script executed:

cat packages/escss-generator/src/generate-values.ts | head -50

Repository: es-js/esjs

Length of output: 1441


🏁 Script executed:

rg "hide" --type=ts packages/escss* | head -20

Repository: es-js/esjs

Length of output: 36


🏁 Script executed:

cat packages/escss-generator/src/generate-values.ts | tail -50

Repository: es-js/esjs

Length of output: 1310


🏁 Script executed:

rg "'hide'|\"hide\"" --type=ts packages/escss*

Repository: es-js/esjs

Length of output: 36


🏁 Script executed:

cat packages/escss/src/utils/transformValue.ts

Repository: es-js/esjs

Length of output: 2228


🏁 Script executed:

cat packages/escss/test/compile.test.ts | sed -n '158,174p'

Repository: es-js/esjs

Length of output: 657


🏁 Script executed:

grep -n "empty-cells" packages/escss/src -r

Repository: es-js/esjs

Length of output: 199


La prueba espera CSS inválido: empty-cells: hidden debe ser empty-cells: hide.

Según la especificación CSS, la propiedad empty-cells acepta únicamente los valores show o hide. El valor hidden no es válido para esta propiedad.

El problema está en el diccionario de traducción: oculto se mapea globalmente a hidden, pero la propiedad empty-cells requiere hide. La función transformValue() no tiene lógica sensible al contexto (no sabe qué propiedad se está transformando), por lo que aplica el mapeo incorrecto.

Acciones necesarias:

  1. Actualizar la prueba para esperar empty-cells: hide en lugar de empty-cells: hidden
  2. Añadir una traducción para hide en el diccionario
  3. Implementar mapeo contextual o crear reglas específicas por propiedad para manejar casos donde oculto debe traducirse a hide en lugar de hidden
🤖 Prompt for AI Agents
In `@packages/escss/test/compile.test.ts` around lines 158 - 174, The test is
asserting an invalid CSS value: change the expectation in compile.test.ts from
'empty-cells: hidden' to 'empty-cells: hide'; then update the translation
dictionary (the value mapping that currently maps 'oculto' -> 'hidden') to
include a 'hide' target and add a property-specific rule so transformValue() can
return 'hide' when the property is 'empty-cells' (leave general mapping for
other properties as 'hidden' if needed); locate and modify transformValue() to
accept property context (or add a small conditional branch keyed on the property
name) so the compile(input, ...) path produces 'empty-cells: hide'.

Comment on lines +203 to +250
describe('CSS to EsCSS', () => {
it('should transform basic properties', () => {
const input = `.card {
display: flex;
width: 100%;
}`

const output = compile(input, { from: 'css', to: 'escss' })

expect(output).toContain('mostrar: flex')
expect(output).toContain('ancho: 100%')
})

it('should transform pseudo-classes', () => {
const input = `.button:hover {
background-color: blue;
}`

const output = compile(input, { from: 'css', to: 'escss' })

expect(output).toContain(':encima')
})

it('should transform @media queries', () => {
const input = `@media (min-width: 768px) {
.container {
width: 100%;
}
}`

const output = compile(input, { from: 'css', to: 'escss' })

expect(output).toContain('@medios')
expect(output).toContain('minimo-ancho')
})

it('should transform properties to ñ tokens', () => {
const input = `.element {
font-size: 16px;
background-size: cover;
}`

const output = compile(input, { from: 'css', to: 'escss' })

expect(output).toContain('fuente-tamaño: 16px')
expect(output).toContain('fondo-tamaño: cubrir')
})
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Cobertura asimétrica: la dirección CSS → EsCSS tiene muy pocos tests.

Solo hay 4 tests para CSS→EsCSS frente a 14 para EsCSS→CSS. Faltan tests inversos para: valores (blockbloque, colores), @keyframes, pseudo-elementos, bordes, y propiedades de grid/flex. La bidireccionalidad es una característica clave del compilador y debería tener una cobertura comparable en ambas direcciones.

🤖 Prompt for AI Agents
In `@packages/escss/test/compile.test.ts` around lines 203 - 250, The CSS→EsCSS
suite ("describe('CSS to EsCSS')") lacks coverage compared to the reverse
direction; add tests calling compile(input, { from: 'css', to: 'escss' }) that
assert translations for missing cases: semantic value mappings (e.g.,
block→bloque and common color names/hex), `@keyframes` rules, pseudo-elements
(::before/::after → their EsCSS tokens), border shorthand/individual border
properties, and grid/flex properties (e.g., display: grid/flex, grid-template,
justify-content). For each case add a focused it(...) using the same pattern as
existing tests and assert with expect(output).toContain(...) referencing the
expected EsCSS tokens so bidirectional parity is validated.

Comment on lines +252 to +303
describe('Edge cases', () => {
it('should preserve unknown properties', () => {
const input = `.elemento {
--custom-property: value;
unknown-property: test;
}`

const output = compile(input, { from: 'escss', to: 'css' })

expect(output).toContain('--custom-property: value')
expect(output).toContain('unknown-property: test')
})

it('should preserve values with functions', () => {
const input = `.elemento {
ancho: calc(100% - 20px);
fondo-imagen: url("test.jpg");
}`

const output = compile(input, { from: 'escss', to: 'css' })

expect(output).toContain('width: calc(100% - 20px)')
expect(output).toContain('background-image: url("test.jpg")')
})

it('should return same content when from equals to', () => {
const input = `.elemento { display: flex; }`

const output = compile(input, { from: 'css', to: 'css' })

expect(output).toBe(input)
})

it('should handle ñ in media query properties', () => {
const input = `@medios (maximo-ancho: 768px) {
.elemento {
fuente-tamaño: 14px;
}
}`

const output = compile(input, { from: 'escss', to: 'css' })

expect(output).toContain('@media')
expect(output).toContain('max-width: 768px')
expect(output).toContain('font-size: 14px')
})

it('should handle empty input', () => {
const output = compile('', { from: 'escss', to: 'css' })
expect(output).toBe('')
})
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Falta un test de ida y vuelta (round-trip) para validar la idempotencia bidireccional.

Un test que compile EsCSS→CSS y luego el resultado CSS→EsCSS (y viceversa) verificaría que no se pierda información en el proceso. Esto es especialmente útil para detectar traducciones inconsistentes o ambiguas.

♻️ Ejemplo de test round-trip
+    it('should round-trip EsCSS → CSS → EsCSS', () => {
+      const input = `.tarjeta {
+  mostrar: flex;
+  ancho: 100%;
+}`
+      const css = compile(input, { from: 'escss', to: 'css' })
+      const roundTrip = compile(css, { from: 'css', to: 'escss' })
+
+      expect(roundTrip).toContain('mostrar: flex')
+      expect(roundTrip).toContain('ancho: 100%')
+    })
🤖 Prompt for AI Agents
In `@packages/escss/test/compile.test.ts` around lines 252 - 303, Add a
bidirectional round-trip test that uses the compile(...) helper to convert
EsCSS→CSS and then compile the resulting CSS back to EsCSS (and a separate case
CSS→EsCSS→CSS), asserting the final string equals the original (or a normalized
form e.g., trimmed/consistent whitespace). Locate tests near the existing "Edge
cases" block and add two it(...) cases referencing the compile function: one
that starts with an EsCSS input, compiles to CSS and back to EsCSS and asserts
equality, and one that starts with CSS and round-trips to CSS; ensure any
normalization used for comparison is applied consistently to both original and
round-tripped outputs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants