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>
This commit is contained in:
Paul Donald
2026-02-16 01:42:55 +01:00
parent 44fd0155ff
commit 4900913d2a
3 changed files with 246 additions and 16 deletions

View File

@@ -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
# Currently, we lint JSON only.
- name: Run ESLint
run: npx eslint **/*.json
# - 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
# Until then, do it simply so we can see which error relates to which file
npx eslint $FILES

View File

@@ -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"],
plugins: { json },
language: "json/json",
extends: ["json/recommended"],
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'],
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
},
}
]);

View File

@@ -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"
}
}