summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Donald2026-02-16 00:42:55 +0000
committerPaul Donald2026-02-16 00:42:55 +0000
commit4900913d2a0f98d5035edaa044ecc80353b7e079 (patch)
tree8a7ee150ac59548f07f3794bffa0d5f5c752bbad
parent44fd0155ff7885bf1c5a5fbea22c7663bc193470 (diff)
downloadluci-4900913d2a0f98d5035edaa044ecc80353b7e079.tar.gz
github: add linting for JS and MD
bump checkout to 6 Included is a selection of rules which flag common problems. Lint -JS ( ESlint ) -JSON ( https://github.com/eslint/json ) -JSDoc (comment matter) in the luci-base module -Markdown ( https://github.com/eslint/markdown ) For JSON, mandate standard JSON (not JSONC or JSON5) format, **/*.json checks across the whole repo. For JS, mandate sourceType: 'script', otherwise the linter errors out about return methods in modules which masks other problems. There are a number of structural design changes needed to bring this repo into compliance with standards. Each JS file is an individual script, despite their module like structure, so functions and classes offloaded to 'common' files (individual scripts and not modules) are invisible to linters unless we define them under the 'globals' key. Custom rules and parsers are also possible. For JSDoc, mandate some common checks to ensure the repo will generate consistent documentation which links and displays well. For MD, check also JS syntax inside MD, as well as MD itself. JS checks inside MD are not strict. Included also eslint-formatter-gha (-f gha) which comments on PRs when problems are detected in code additions. Actions show green when no errors are reported (warnings are a pass) but the gha will comment about warnings. Flag `--diff-filter=ACM` shows only additions, and not deletions. Signed-off-by: Paul Donald <newtwen+github@gmail.com>
-rw-r--r--.github/workflows/eslint.yml60
-rw-r--r--eslint.config.mjs190
-rw-r--r--package.json10
3 files changed, 245 insertions, 15 deletions
diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml
index f0fe323187..0626f07353 100644
--- a/.github/workflows/eslint.yml
+++ b/.github/workflows/eslint.yml
@@ -1,15 +1,21 @@
---
-name: "LuCI repo ESLint JSON Analysis"
+name: "LuCI repo ESLint JS/ON and MD Analysis"
on:
push:
branches: [ "master" ]
path:
- '**/*.json'
+ - '**/*.js'
+ - '**/*.md'
+
pull_request:
branches: [ "master" ]
path:
- '**/*.json'
+ - '**/*.js'
+ - '**/*.md'
+
permissions: {}
jobs:
@@ -17,17 +23,57 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out repository
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
+ with:
+ fetch-depth: 2
- name: Set up Node.js
- uses: actions/setup-node@v5
+ uses: actions/setup-node@v6
with:
node-version: latest
+ # eslint/json requires eslint@9 - 10 is coming.
- name: Install ESLint
- run: npm install --no-audit --no-fund --save-dev eslint@latest @eslint/json@latest
+ run: |
+ npm install --no-audit --no-fund --save-dev \
+ eslint@9 \
+ @eslint/json@latest \
+ @eslint/js \
+ @eslint/markdown \
+ eslint-formatter-gha
+
+ # - name: Run ESLint (on whole repo)
+ # run: npx eslint .
+
+ - name: Run ESLint on changed files
+ run: |
+ if [ "${{ github.event_name }}" = "pull_request" ]; then
+ BASE="${{ github.event.pull_request.base.sha }}"
+ HEAD="${{ github.event.pull_request.head.sha }}"
+ else
+ # push event: always diff last commit
+ BASE="$(git rev-parse HEAD~1 2>/dev/null || true)"
+ HEAD="$(git rev-parse HEAD)"
+ fi
+
+ if [ -z "$BASE" ]; then
+ FILES=$(git ls-files '*.js' '*.json' '*.md')
+ else
+ FILES=$(git diff --diff-filter=ACM --name-only "$BASE" "$HEAD" \
+ | grep -E '\.(js|json|md)$' || true)
+ fi
+
+ if [ -z "$FILES" ]; then
+ echo "No JS/JSON or MD files changed"
+ exit 0
+ fi
+
+ echo "Linting files:"
+ echo "$FILES"
+
+ # One day we might need xargs with huge lists
+ # echo "$FILES" | xargs npx eslint -f gha
- # Currently, we lint JSON only.
- - name: Run ESLint
- run: npx eslint **/*.json
+ # Until then, do it simply so we can see which error relates to which file
+ npx eslint $FILES
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 87f38a6e44..36ba788108 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -1,13 +1,189 @@
-import { defineConfig } from "eslint/config";
-import json from "@eslint/json";
+import { defineConfig, globalIgnores } from 'eslint/config';
+import globals from 'globals';
+import markdown from "@eslint/markdown";
+import jsdoc from 'eslint-plugin-jsdoc';
+import json from '@eslint/json';
+import js from '@eslint/js';
+
+console.log("loaded luci repo eslint.config.mjs");
+
+export const jsdoc_less_relaxed_rules = {
+ // 0: off, 1: warn, 2: error
+ /* --- JSDoc correctness --- */
+ 'jsdoc/check-alignment': 'warn',
+ 'jsdoc/check-param-names': 'off',
+ 'jsdoc/check-tag-names': 'warn',
+ 'jsdoc/check-types': 'off',
+ 'jsdoc/no-defaults': 'off',
+ 'jsdoc/reject-any-type': 'off',
+ 'jsdoc/require-jsdoc': 'off',
+ 'jsdoc/require-param': 'warn',
+ 'jsdoc/require-returns': 'warn',
+ 'jsdoc/require-returns-check': 'off',
+ 'jsdoc/require-returns-type': 'warn',
+ 'jsdoc/tag-lines': 'off',
+ 'no-shadow-restricted-names': 'off',
+
+ /* --- Style --- */
+ 'jsdoc/require-description': 'off',
+ 'jsdoc/require-param-description': 'off',
+ 'jsdoc/require-returns-description': 'off',
+
+ /* --- custom classes and types --- */
+ 'jsdoc/no-undefined-types': 'warn', // custom LuCI types
+ 'jsdoc/valid-types': 'warn',
+}
+
+export const jsdoc_relaxed_rules = {
+ // 0: off, 1: warn, 2: error
+ /* --- JSDoc correctness --- */
+ 'jsdoc/check-alignment': 'warn',
+ 'jsdoc/check-param-names': 'off',
+ 'jsdoc/check-tag-names': 'warn',
+ 'jsdoc/check-types': 'off',
+ 'jsdoc/no-defaults': 'off',
+ 'jsdoc/reject-any-type': 'off',
+ 'jsdoc/require-jsdoc': 'off',
+ 'jsdoc/require-param': 'warn',
+ 'jsdoc/require-returns': 'warn',
+ 'jsdoc/require-returns-check': 'off',
+ 'jsdoc/require-returns-type': 'warn',
+ 'jsdoc/tag-lines': 'off',
+ 'no-shadow-restricted-names': 'off',
+
+ /* --- Style --- */
+ 'jsdoc/require-description': 'off',
+ 'jsdoc/require-param-description': 'off',
+ 'jsdoc/require-returns-description': 'off',
+
+ /* --- custom classes and types --- */
+ 'jsdoc/no-undefined-types': 'off', // custom LuCI types
+ 'jsdoc/valid-types': 'off',
+}
export default defineConfig([
+ globalIgnores([
+ 'docs',
+ 'node_modules',
+ ]),
+ // Markdown
{
- files: ["**/*.json"],
- ignores: ["package-lock.json"],
+ files: ["**/*.md"],
+ plugins: {
+ markdown,
+ },
+ processor: "markdown/markdown",
+ },
+ // applies only to JavaScript blocks inside of Markdown files
+ {
+ files: ["**/*.md/*.js"],
+ rules: {
+ strict: "off",
+ },
+ },
+ // JSON files
+ {
+ files: ['**/*.json'],
+ ignores: ['package-lock.json'],
plugins: { json },
- language: "json/json",
- extends: ["json/recommended"],
+ language: 'json/json',
+ extends: ['json/recommended'],
+ rules: {
+ 'json/no-duplicate-keys': 'error',
+ },
},
+ // JavaScript files
+ {
+ files: ['**/*.js'],
+ language: '@/js',
+ plugins: { js },
+ extends: ['js/recommended'],
+ linterOptions:{
+ // silence warnings about inert // eslint-disable-next-line xxx
+ reportUnusedDisableDirectives: "off",
+ },
+ languageOptions: {
+ sourceType: 'script',
+ ecmaVersion: 2026, // 2015 == ECMA6
+ globals: {
+ ...globals.browser,
+ /* LuCI runtime / cbi exports */
+ _: 'readonly',
+ N_: 'readonly',
+ L: 'readonly',
+ E: 'readonly',
+ TR: 'readonly',
+ cbi_d: 'readonly',
+ cbi_strings: 'readonly',
+ cbi_d_add: 'readonly',
+ cbi_d_check: 'readonly',
+ cbi_d_checkvalue: 'readonly',
+ cbi_d_update: 'readonly',
+ cbi_init: 'readonly',
+ cbi_update_table: 'readonly',
+ cbi_validate_form: 'readonly',
+ cbi_validate_field: 'readonly',
+ cbi_validate_named_section_add: 'readonly',
+ cbi_validate_reset: 'readonly',
+ cbi_row_swap: 'readonly',
+ cbi_tag_last: 'readonly',
+ cbi_submit: 'readonly',
+ cbi_dropdown_init: 'readonly',
+ isElem: 'readonly',
+ toElem: 'readonly',
+ matchesElem: 'readonly',
+ findParent: 'readonly',
+ sfh: 'readonly',
+ renderBadge: 'readonly', // found in theme templates
+ /* modules */
+ baseclass: 'readonly',
+ dom: 'readonly',
+ firewall: 'readonly',
+ fwtool: 'readonly',
+ form: 'readonly',
+ fs: 'readonly',
+ network: 'readonly',
+ nettools: 'readonly',
+ poll: 'readonly',
+ random: 'readonly',
+ request: 'readonly',
+ session: 'readonly',
+ rpc: 'readonly',
+ uci: 'readonly',
+ ui: 'readonly',
+ uqr: 'readonly',
+ validation: 'readonly',
+ view: 'readonly',
+ widgets: 'readonly',
+ /* dockerman */
+ dm2: 'readonly',
+ jsapi: 'readonly',
+ },
+ parserOptions: {
+ ecmaFeatures: {
+ globalReturn: true,
+ }
+ },
+ },
+ rules: { // 0: off, 1: warn, 2: error
+ 'strict': 0,
+ 'no-prototype-builtins': 0,
+ 'no-empty': 0,
+ 'no-undef': 'warn',
+ 'no-unused-vars': ['off', { "caughtErrors": "none" }],
+ 'no-regex-spaces': 0,
+ 'no-control-regex': 0,
+ }
+ },
+ {
+ extends: ['jsdoc/recommended'], // run jsdoc recommended rules
+ files: ['modules/luci-base/**/*.js'], // ... but only on these js files
+ plugins: { jsdoc },
+ rules: {
+ /* use these settings when linting the checked out repo */
+ // ...jsdoc_less_relaxed_rules
+ /* ... and use these settings for the repo (less noisy) */
+ ...jsdoc_relaxed_rules
+ },
+ }
]);
-
diff --git a/package.json b/package.json
index 220b126af6..59e3d0fffa 100644
--- a/package.json
+++ b/package.json
@@ -5,8 +5,16 @@
},
"devDependencies": {
"@alphanull/jsdoc-vision-theme": "^1.2.2",
+ "@eslint/js": "^9.39.2",
+ "@eslint/json": "^1.0.1",
+ "@eslint/markdown": "^7.5.1",
"clean-jsdoc-theme": "^4.3.0",
+ "eslint": "^9.39.2",
+ "eslint-formatter-gha": "^2.0.1",
+ "eslint-plugin-jsdoc": "^62.5.5",
+ "globals": "^17.3.0",
"jaguarjs-jsdoc": "^1.1.0",
- "jsdoc": "^4.0.5"
+ "jsdoc": "^4.0.5",
+ "neostandard": "^0.12.2"
}
}