Skip to content

Commit e2aa8bb

Browse files
authored
chore(lint): let the linter see tests/ and scripts/ (#783)
`npm run lint` was `eslint src`, so two whole trees were never linted. Across the fleet that hid roughly 3,900 errors, none of which any CI leg had shown. Most of it was the config, not the code, and the same two defects were in every app. **scripts/ had no config block at all.** These are CommonJS Node CLI checkers, and flat config defaults every `.js` to ESM with browser-ish globals, so eslint read the CommonJS wrapper itself as undefined identifiers: `require`, `process`, `__dirname`, `__filename`, `module`. A `scripts/**` block now declares the environment. Declaring beats suppressing here: `no-undef` is the rule that catches a genuinely misspelled identifier, and dozens of fake findings would bury a real one. A second block covers `scripts/**/*.mjs`, which is ESM and needs Node's globals without the CommonJS wrapper. **The tests block applied a non-TypeScript-aware rule to TypeScript.** It named `tests/**/*.ts` while setting the CORE `no-unused-vars`, which v9 deliberately turns off for `.ts` in favour of the `@typescript-eslint` version. The core rule reads the parameter names inside a function TYPE as bindings, so t?: (app: string, key: string) => string reports `app` and `key` as unused variables, and every unused `catch (e)` in a `.ts` spec reports twice. The block is split now: `.js`/`.mjs` on the core rule, `.ts`/`.tsx` on the TypeScript one, same patterns on both. Also: stale `eslint-disable` comments naming plugins eslint 10 no longer registers, which are themselves errors ("Definition for rule ... was not found"), and a rule that must not parse shell scripts. The genuinely real findings were the useful part: dead locals, unused imports, dead helper functions, unused `catch` bindings, extensionless relative imports, and a handful of `== null` comparisons spelled out so they still match null AND undefined. Verified per app: `npm run lint` 0 errors over src + tests + scripts, `prettier --check` clean, and the unit suite still green.
1 parent bdb30f1 commit e2aa8bb

46 files changed

Lines changed: 1273 additions & 351 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

eslint.config.mjs

Lines changed: 134 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -144,11 +144,32 @@ export default [
144144
'no-console': 'off',
145145
'n/no-process-exit': 'off',
146146
'n/hashbang': 'off',
147-
// `_` / `__` as a deliberate throwaway binding — `catch (_)`, a
148-
// discarded destructuring slot. Narrow on purpose: the pattern matches
149-
// UNDERSCORES ONLY, so a real name that happens to start with `_` is
150-
// still reported. v9 drives plain `.js` through the CORE rule (the
151-
// `@typescript-eslint` swap is per-file-type), so it is set here.
147+
// Tests import devDependencies by definition; this rule is about what
148+
// ships in the published package, which tests/ never does.
149+
'n/no-unpublished-import': 'off',
150+
},
151+
},
152+
153+
{
154+
// `_` / `__` as a deliberate throwaway binding — `catch (_)`, a discarded
155+
// destructuring slot. Narrow on purpose: the pattern matches UNDERSCORES
156+
// ONLY, so a real name that happens to start with `_` is still reported.
157+
//
158+
// 🔴 `.js` / `.mjs` ONLY, NOT `.ts`. The CORE rule is not TypeScript-aware:
159+
// applied to a `.ts` file it reads the parameter NAMES inside a function
160+
// TYPE as bindings and reports them unused. Measured on humaniq —
161+
//
162+
// t?: (app: string, key: string) => string
163+
//
164+
// produced four `no-unused-vars` errors for `app` and `key`, which are
165+
// documentation, not variables. The same mis-scoping made every unused
166+
// `catch (e)` in a `.ts` spec report TWICE, once per rule.
167+
//
168+
// v9 already turns the core rule off for `.ts` and drives
169+
// `@typescript-eslint/no-unused-vars` instead; naming `.ts` here switched
170+
// it back on. TypeScript files are handled by the block below.
171+
files: ['tests/**/*.js', 'tests/**/*.mjs'],
172+
rules: {
152173
'no-unused-vars': [
153174
'error',
154175
{
@@ -165,12 +186,117 @@ export default [
165186
ignoreRestSiblings: true,
166187
},
167188
],
168-
// Tests import devDependencies by definition; this rule is about what
169-
// ships in the published package, which tests/ never does.
170-
'n/no-unpublished-import': 'off',
171189
},
172190
},
173191

192+
{
193+
// The TypeScript half of the block above. Same intent, same patterns, on
194+
// the rule that actually understands the language: it knows a name inside
195+
// a function type is not a binding, so type annotations stay quiet while a
196+
// genuinely dead local is still reported.
197+
files: ['tests/**/*.ts', 'tests/**/*.tsx'],
198+
rules: {
199+
'@typescript-eslint/no-unused-vars': [
200+
'error',
201+
{
202+
varsIgnorePattern: '^_+$',
203+
caughtErrors: 'all',
204+
caughtErrorsIgnorePattern: '^_+$',
205+
argsIgnorePattern: '^_',
206+
ignoreRestSiblings: true,
207+
},
208+
],
209+
},
210+
},
211+
212+
{
213+
// 🔴 Node-side CLI tooling under `scripts/`, which is COMMONJS. Flat
214+
// config defaults every `.js` to ESM with browser-ish globals, so without
215+
// this block eslint reports the CommonJS wrapper itself as undefined
216+
// identifiers. Measured on this app: 52 of the 233 errors under
217+
// `tests/` + `scripts/` were `no-undef`, ALL of them in `scripts/`, and
218+
// all five names were the environment rather than a typo — `process` 23,
219+
// `require` 20, `__dirname` 6, `__filename` 2, `module` 1.
220+
//
221+
// This is describing the environment, not relaxing a rule, and it is the
222+
// same argument the test-globals block below makes: declaring them keeps
223+
// `no-undef` able to do its real job, which is catching a genuinely
224+
// misspelled identifier. Suppressing the rule instead would bury that.
225+
//
226+
// `no-console` is off because printing its report is what a CLI checker
227+
// is FOR.
228+
//
229+
// 🔴 NO `n/*` ENTRIES HERE, DELIBERATELY. `eslint-plugin-n` is NOT
230+
// registered for these files under eslint 10 + @nextcloud/eslint-config
231+
// 9, so `'n/no-process-exit': 'off'` would be dead config that reads as
232+
// if it were doing something. Measured both ways on this app: 0 `n/`
233+
// findings with the entries and 0 without.
234+
//
235+
// What DID report was the opposite — four `scripts/*.js` carried
236+
// `/* eslint-disable n/no-process-exit */` and `/* eslint-disable
237+
// n/shebang */` left over from the eslintrc era, and an inline disable
238+
// naming an unregistered plugin is itself an error ("Definition for rule
239+
// 'n/shebang' was not found"). Those 8 comments are removed; do not add
240+
// `n/*` rules back to replace them.
241+
//
242+
// ⚠️ `.js` and `.cjs` ONLY. A `scripts/*.mjs` is genuinely ESM and must
243+
// keep the default `sourceType`, or `import` stops parsing there.
244+
files: ['scripts/**/*.js', 'scripts/**/*.cjs'],
245+
languageOptions: {
246+
sourceType: 'commonjs',
247+
globals: {
248+
require: 'readonly',
249+
module: 'writable',
250+
exports: 'writable',
251+
process: 'readonly',
252+
__dirname: 'readonly',
253+
__filename: 'readonly',
254+
console: 'readonly',
255+
Buffer: 'readonly',
256+
global: 'readonly',
257+
URL: 'readonly',
258+
TextEncoder: 'readonly',
259+
TextDecoder: 'readonly',
260+
},
261+
},
262+
rules: {
263+
'no-console': 'off',
264+
},
265+
},
266+
267+
{
268+
// The ESM half of the block above. A `scripts/*.mjs` is genuinely a module
269+
// and must keep the default `sourceType`, so it gets Node's globals but
270+
// none of the CommonJS wrapper. Measured: `process` reported undefined 2x
271+
// in hermiq's generate-opengemeenten-icons.mjs and 4x in openregister's
272+
// l10n/runtime-check.mjs, which the `.js`/`.cjs` block deliberately does
273+
// not match.
274+
files: ['scripts/**/*.mjs', 'tests/**/*.mjs'],
275+
languageOptions: {
276+
globals: {
277+
process: 'readonly',
278+
console: 'readonly',
279+
Buffer: 'readonly',
280+
global: 'readonly',
281+
URL: 'readonly',
282+
TextEncoder: 'readonly',
283+
TextDecoder: 'readonly',
284+
},
285+
},
286+
rules: {
287+
'no-console': 'off',
288+
},
289+
},
290+
291+
{
292+
// eslint must not try to PARSE a shell script. `tests/e2e/seed.test.sh`
293+
// matches the `**/*.test.*` glob some presets use, and eslint then reads
294+
// it as JavaScript and reports "Parsing error: Unexpected character" —
295+
// a finding about a file it should never have opened.
296+
ignores: ['**/*.sh', '**/*.bash'],
297+
},
298+
299+
174300
{
175301
// Test globals. Several apps keep their spec files INSIDE `src/`, which the
176302
// lint script scans, and neither `@nextcloud/eslint-config` nor the runner

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"build": "NODE_ENV=production webpack --config webpack.config.js --progress",
1111
"dev": "NODE_ENV=development webpack --config webpack.config.js --progress",
1212
"watch": "NODE_ENV=development webpack --config webpack.config.js --progress --watch",
13-
"lint": "eslint src",
13+
"lint": "eslint src tests scripts",
1414
"lint-fix": "npm run lint -- --fix",
1515
"stylelint": "stylelint \"src/**/*.{vue,scss,css}\"",
1616
"format": "prettier --check \"**/*.{js,ts,vue,css,scss}\"",

scripts/build-l10n-js.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ function renderJs(id, translations, pluralForm) {
101101
].join('\n')
102102
}
103103

104+
/**
105+
*
106+
*/
104107
function main() {
105108
const check = process.argv.includes('--check')
106109
const id = appId()

scripts/check-schema-l10n.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ function collect(node, where, sink) {
113113
for (const value of Object.values(node)) collect(value, where, sink)
114114
}
115115

116+
/**
117+
*
118+
*/
116119
function main() {
117120
const update = process.argv.includes('--update')
118121
const list = process.argv.includes('--list')

tests/e2e/credential-verify.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { test, expect } from './fixtures'
1+
import { expect, test } from './fixtures.ts'
22

33
/**
44
* Credential verification page tests.

tests/e2e/docs-screenshots.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@
3737
* Pattern reference: ADR-030 (hydra/openspec/architecture/).
3838
*/
3939

40-
import { test, expect, type Page } from '@playwright/test'
41-
import * as path from 'path'
40+
import type { Page } from '@playwright/test'
41+
42+
import { expect, test } from '@playwright/test'
4243
import * as fs from 'fs'
44+
import * as path from 'path'
4345

4446
const SHOT_ROOT = path.resolve(
4547
__dirname,

tests/e2e/fixtures.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { test as base, Page } from '@playwright/test'
1+
import type { Page } from '@playwright/test'
2+
3+
import { test as base } from '@playwright/test'
24

35
/**
46
* Navigate to the app and verify the session is active.

tests/e2e/global-setup.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { chromium } from '@playwright/test'
22
import { execFileSync } from 'child_process'
33
import * as fs from 'fs'
44
import * as path from 'path'
5-
import { baseUrl } from './base-url'
5+
import { baseUrl } from './base-url.ts'
66

77
/** Repo root — `tests/e2e/` is two levels down. */
88
const APP_ROOT = path.resolve(__dirname, '..', '..')
@@ -152,7 +152,7 @@ async function globalSetup(): Promise<void> {
152152
'cn-walkthrough-seen:learniq',
153153
'999.0.0',
154154
)
155-
} catch (e) {
155+
} catch {
156156
// localStorage unavailable — specs fall back to dismissing by hand.
157157
}
158158
})

tests/e2e/l10n-browser-catalogue.spec.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,10 @@
3939
* nothing about apps whose translations differ.
4040
*/
4141

42+
import { expect, test } from '@playwright/test'
4243
import { readFileSync } from 'node:fs'
4344
import path from 'node:path'
4445

45-
import { expect, test } from '@playwright/test'
46-
4746
/**
4847
* The app id this repo declares. Resolved from the repo root by walking up
4948
* from this file, so it does not depend on the working directory playwright

tests/e2e/pages.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { test, expect } from './fixtures'
1+
import { expect, test } from './fixtures.ts'
22

33
/**
44
* Page-level smoke tests — navigate to every manifest route and assert that:

0 commit comments

Comments
 (0)