diff --git a/.gitignore b/.gitignore index c1909b2..3cd1345 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,11 @@ playwright-report/ # Dependencies (test-only) node_modules/ +# Lint / typecheck caches +.eslintcache +.stylelintcache +*.tsbuildinfo + # Local server / scratch .cache/ tmp/ diff --git a/.htmlvalidate.json b/.htmlvalidate.json new file mode 100644 index 0000000..ba6657f --- /dev/null +++ b/.htmlvalidate.json @@ -0,0 +1,10 @@ +{ + "extends": ["html-validate:recommended"], + "rules": { + "void-style": "off", + "no-inline-style": "off", + "no-trailing-whitespace": "warn", + "wcag/h32": "off", + "no-implicit-button-type": "warn" + } +} diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 0000000..4bef5eb --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,37 @@ +{ + "rules": { + "block-no-empty": true, + "no-empty-source": true, + "no-duplicate-at-import-rules": true, + "no-invalid-double-slash-comments": true, + "no-invalid-position-at-import-rule": true, + "no-irregular-whitespace": true, + "comment-no-empty": true, + "font-family-no-missing-generic-family-keyword": true, + "function-calc-no-unspaced-operator": true, + "function-linear-gradient-no-nonstandard-direction": true, + "function-no-unknown": true, + "keyframe-declaration-no-important": true, + "media-query-no-invalid": true, + "named-grid-areas-no-invalid": true, + "selector-anb-no-unmatchable": true, + "selector-pseudo-class-no-unknown": true, + "selector-pseudo-element-no-unknown": true, + "selector-type-no-unknown": true, + "string-no-newline": true, + "unit-no-unknown": true, + "color-no-invalid-hex": true, + "declaration-block-no-duplicate-properties": [ + true, + { "ignore": ["consecutive-duplicates-with-different-values"] } + ], + "declaration-block-no-shorthand-property-overrides": true, + "property-no-unknown": true, + "at-rule-no-unknown": true + }, + "ignoreFiles": [ + "node_modules/**", + "playwright-report/**", + "test-results/**" + ] +} diff --git a/README.md b/README.md index 34b7e80..f23a38a 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,30 @@ BASE_URL=https://iliadobkin.com npx playwright test --- +## Lint & quality checks + +The site has no build step, but the dev toolchain runs a four-step quality pass on every commit-worthy change. All checks have an npm script and can be run individually. + +```bash +npm run lint # eslint + stylelint + html-validate, in parallel +npm run lint:js # ESLint over js/, scripts/, tests/, *.ts (vanilla JS + TS) +npm run lint:css # Stylelint over css/*.css (bug-only ruleset, no style nags) +npm run lint:html # html-validate over index.html (a11y, roles, DOCTYPE) +npm run typecheck # tsc --noEmit on playwright.config.ts + tests/*.ts +npm run check # lint + typecheck + test (the full guardrail set) +``` + +Conventions worth knowing: + +- **ESLint** uses a flat config (`eslint.config.mjs`) with three scopes — browser globals for `js/`, Node ESM for `scripts/`, and `typescript-eslint` for `tests/` + `*.ts`. `console.warn` / `console.error` are allowed; `console.log` is not. +- **Stylelint** runs a minimal "bug-only" ruleset (`.stylelintrc.json`) — block-no-empty, no-invalid-hex, function-no-unknown, property-no-unknown, etc. Compact handwritten CSS (single-line rules, `rgba()`, 6-char hex) is intentional and not flagged. +- **html-validate** (`.htmlvalidate.json`) enforces uppercase DOCTYPE, accessible button names, non-redundant ARIA roles, and valid landmark usage. +- **tsc** (`tsconfig.json`) runs in `--noEmit` strict mode against the test files — fails fast if a selector signature drifts. + +`npm-run-all2` parallelizes the linters for speed (`npm run lint` finishes in ~3s); `npm run check` is the one to wire into a Gitea Actions runner. + +--- + ## Editing content **All content lives in [`js/data.js`](js/data.js)** under `window.PORTFOLIO`. Change a title, swap a job, retag a skill — the UI updates automatically because every section is rendered from this single object. diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..bbaabba --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,89 @@ +// Flat config — ESLint 9. Three lint targets: +// 1. Browser vanilla JS in `js/` (no build, no modules) +// 2. Node ES modules in `scripts/` (utility tasks) +// 3. TypeScript test + config files (@playwright/test) +import js from "@eslint/js"; +import globals from "globals"; +import tseslint from "typescript-eslint"; + +export default [ + { + ignores: [ + "node_modules/", + "playwright-report/", + "test-results/", + "assets/", + "dist/", + ".cache/", + ], + }, + + // ----- Browser vanilla JS (js/app.js, js/data.js) ----- + { + files: ["js/**/*.js"], + languageOptions: { + ecmaVersion: 2022, + sourceType: "script", + globals: { + ...globals.browser, + // `data.js` exposes window.PORTFOLIO and a set of `renderXxx` + // top-level functions referenced from inline `render:` properties. + // They are looked up by reference inside data.js, not by name from + // other files, so we don't need to pre-declare each renderer. + PORTFOLIO: "readonly", + }, + }, + rules: { + ...js.configs.recommended.rules, + eqeqeq: ["error", "always"], + "no-var": "error", + "prefer-const": "warn", + "no-console": ["warn", { allow: ["warn", "error"] }], + // _ / _esc are intentional helper / placeholder names. + "no-unused-vars": [ + "warn", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + caughtErrorsIgnorePattern: "^_", + }, + ], + }, + }, + + // ----- Node ES modules (scripts/*.mjs) ----- + { + files: ["scripts/**/*.{js,mjs}"], + languageOptions: { + ecmaVersion: 2022, + sourceType: "module", + globals: { ...globals.node }, + }, + rules: { + ...js.configs.recommended.rules, + eqeqeq: ["error", "always"], + "no-var": "error", + "prefer-const": "warn", + "no-console": "off", + }, + }, + + // ----- TypeScript (tests/, playwright.config.ts) ----- + ...tseslint.configs.recommended.map((cfg) => ({ + ...cfg, + files: ["tests/**/*.ts", "*.ts"], + })), + { + files: ["tests/**/*.ts", "*.ts"], + languageOptions: { + globals: { ...globals.node }, + }, + rules: { + "@typescript-eslint/no-unused-vars": [ + "warn", + { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, + ], + "@typescript-eslint/no-explicit-any": "off", + }, + }, +]; diff --git a/index.html b/index.html index b24065c..d34e39c 100644 --- a/index.html +++ b/index.html @@ -1,4 +1,4 @@ - + @@ -14,9 +14,9 @@ -