Skip to content

ci(js): lint the backend JavaScript, which nothing did - #855

Merged
CybotTM merged 1 commit into
mainfrom
ci/js-lint
Aug 20, 2026
Merged

ci(js): lint the backend JavaScript, which nothing did#855
CybotTM merged 1 commit into
mainfrom
ci/js-lint

Conversation

@CybotTM

@CybotTM CybotTM commented Aug 20, 2026

Copy link
Copy Markdown
Member

Closes #825. A file whose methods had landed outside their class passed the whole CI matrix — 71 green checks — and the rating buttons it shipped would not have worked. PHPStan does not read JavaScript, the functional suite renders the template without executing the module, and no Playwright spec covers that view. A person reading the diff found it.

node --check lost on evidence, not on taste

It was the cheap candidate: no dependency, no config. Tested against the actual broken file, it accepted it — because it parses a .js file as a CommonJS script. ESLint parses it as an ES module, which is how the browser loads it, and reports the parse error on the right line.

That also settles something left open in #842's review. I had written there that I could not reconcile node --check passing with a minimal reproduction failing, and would not dress it up. The answer is the parsing mode: the file was a syntax error all along, and node --check read it in the wrong one.

My reason for not deciding this earlier was wrong

I had said eslint "would bring a JS toolchain into a PHP extension that has none". One command disproves it: package.json, package-lock.json, three devDependencies including Playwright, and CI already runs npm install for the e2e job. ESLint is one more line in a file that exists.

No sibling extension lints JavaScriptrte_ckeditor_image has a package.json for commitlint and husky only, nr-vault one for Playwright, nr-repurpose none at all. There was no house standard to copy, which is a reason to set one rather than to wait for it.

The rule set is small on purpose

no-undef is an error: it is the rule that catches the defect above.

no-unsanitized is loaded but set to warn, and the reason is measured rather than cautious. As an error it reports 12 assignments, and none of them is a hole — the code escapes through escapeHtml(), visibly, with a comment saying so:

// SECURITY: Escape all external data to prevent XSS
const safeProviderName = escapeHtml(providerInfo.suggestedName);

The rule recognises a fixed set of sanitizers and a helper of ours is not among them. Dropping the plugin was not an option either: two files already carry eslint-disable-line no-unsanitized/property — suppressions written for a rule that never ran, which become "rule not found" errors the moment linting is switched on.

Those twelve are triaged in #854 with the evidence and three options. A security question belongs where it can be argued and closed, not in a config comment.

Verification

clean tree 0 errors, 14 warnings, exit 0
#825's shape reintroduced (method after the class's closing brace) parse error, exit 1
npm ci from the lockfile, then lint exit 0

Repo rules: no job is defined here — thin caller of the shared script-check reusable, check-workflow-ownership.php passes, and drift_compare.py against the org template reports no drift. pre-command installs the devDependencies the runner does not ship.

One thing worth knowing beyond this repo

This makes nr_llm the first Netresearch TYPO3 extension with a JavaScript gate. If it holds up, it belongs in the shared template rather than being rebuilt per repository — otherwise the next extension gets the same 71-green-checks surprise.

Assisted by claude-code:claude-opus-5 — Session

A file whose methods had landed outside their class passed the whole CI
matrix — 71 green checks — and the rating buttons it shipped would not
have worked. PHPStan does not read JavaScript, the functional suite
renders the template without executing the module, and no Playwright spec
covers that view. A person reading the diff found it.

node --check was the cheaper candidate and lost on evidence, not on
taste: it parses a .js file as a CommonJS script and ACCEPTED the broken
file, while ESLint parses it as an ES module — which is how the browser
loads it — and reports the parse error on the right line. That also
settles a contradiction left open in #842's review: the file was a syntax
error all along; node --check simply read it in the wrong mode.

My stated reason for not deciding this earlier was wrong, and checking it
took one command: "eslint would bring a JS toolchain into a PHP
extension". package.json, package-lock.json and three devDependencies
were already there, and CI already runs npm install for the e2e job.

No sibling extension lints JavaScript — rte_ckeditor_image has a
package.json for commitlint only, nr-vault one for Playwright,
nr-repurpose none. There was no house standard to follow, which is a
reason to set one rather than to wait.

The rule set is small on purpose. no-undef is the error, because that is
the rule that catches the defect above. no-unsanitized is loaded but set
to warn: as an error it reports 12 assignments and NONE is a hole — the
code escapes through escapeHtml(), visibly and with a comment saying so,
and the rule cannot recognise a helper of ours as a sanitizer. Dropping
the plugin was not an option either: two files already carry
eslint-disable-line no-unsanitized/property, suppressions written for a
rule that never ran, which become "rule not found" errors the moment
linting is switched on. Those twelve are triaged in #854 with the
evidence, because a security question belongs where it can be argued.

The clean tree lints at 0 errors. Reintroducing the #825 shape — a method
after the class's closing brace — fails with a parse error and exit 1.

Thin caller of the shared script-check reusable, so no job is defined
here; pre-command installs the devDependencies the runner does not ship.

Closes #825

Signed-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>
Copilot AI lite review requested due to automatic review settings August 20, 2026 20:37
@CybotTM
CybotTM requested a review from a team as a code owner August 20, 2026 20:37
@github-actions github-actions Bot added ci CI/CD changes dependencies Dependency updates labels Aug 20, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Automated approval for maintainer PR

All automated quality gates passed. See SECURITY_CONTROLS.md for compensating controls.

@sonarqubecloud

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

Copy link
Copy Markdown
Contributor

Dependency Review

The following issues were found:

  • ✅ 0 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ✅ 0 package(s) with invalid SPDX license definitions
  • ⚠️ 1 package(s) with unknown licenses.
  • ⚠️ 17 packages with OpenSSF Scorecard issues.

View full job summary

@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.56%. Comparing base (1c46f74) to head (633798d).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##               main     #855      +/-   ##
============================================
- Coverage     86.71%   86.56%   -0.15%     
- Complexity     9916    10009      +93     
============================================
  Files           574      574              
  Lines         32049    32215     +166     
============================================
+ Hits          27792    27888      +96     
- Misses         4257     4327      +70     
Flag Coverage Δ
unit 65.14% <ø> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.
see 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@CybotTM

CybotTM commented Aug 20, 2026

Copy link
Copy Markdown
Member Author

Review record — no Copilot review exists (monthly, account-wide quota), so the green gate is not a read of the change.

The check I would not have thought to make, and did: js-lint / check: pass appears in this PR's own check list. A new gate that is configured but never triggers is the declaration-nothing-reads failure in its purest form, and "the PR is green" would have hidden it — the other 74 checks are green with or without it.

The rule choice is measured, not preferred. node --check was the cheaper candidate and I tested it against the actual broken file from #842: it accepted it, because a .js file parses as a CommonJS script there. ESLint parses it as an ES module — the mode the browser uses — and reports the parse error on the right line. That also closes a question I explicitly left open in #842's review rather than guessing at.

The control was run in both directions. Clean tree: 0 errors, exit 0. #825's shape reintroduced — a method after the class's closing brace — parse error, exit 1. And npm ci from the committed lockfile followed by a lint run: exit 0, so the gate does not depend on my local node_modules.

Twelve sanitizer findings are deliberately warnings, and the reason is in #854, not here. None of them is a hole: the code escapes through escapeHtml() with a comment saying so, and the rule cannot recognise a helper of ours as a sanitizer. Loading the plugin anyway is not optional — two files carry eslint-disable-line no-unsanitized/property for a rule that never ran, and those become errors the moment linting starts.

One correction to my own earlier reasoning, stated in the PR body too: I had declined to decide this on the grounds that eslint would introduce a JS toolchain into a PHP extension. package.json, a lockfile and three devDependencies were already there. One command would have told me.

Repo rules: no job defined here (thin caller of the shared reusable), check-workflow-ownership.php passes, template drift reports none.

Merging on that basis.

Assisted by claude-code:claude-opus-5 — Session

@CybotTM

CybotTM commented Aug 20, 2026

Copy link
Copy Markdown
Member Author

Self-review: 633798d

The review this pull request demands is unsatisfiable (Copilot quota wall or repeated bot failures on this head). Per the documented fallback, the diff on this head was reviewed by the PR author; this comment is the on-the-record attestation the merge gate reads back. It stops matching on the next push.

@CybotTM
CybotTM added this pull request to the merge queue Aug 20, 2026
Merged via the queue into main with commit 94ce8b2 Aug 20, 2026
95 of 96 checks passed
@CybotTM
CybotTM deleted the ci/js-lint branch August 20, 2026 21:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci CI/CD changes dependencies Dependency updates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Nothing checks the backend JavaScript, and a broken file passed the whole matrix

2 participants