Skip to content

Commit 41a0afe

Browse files
committed
feat: add oxlint configuration and custom rule plugins
Add .oxlintrc.json (workspace root and regression-test), porting the ESLint ruleset to oxlint and covering gaps with two custom rules and a jsPlugins bridge: - react-js/* (jsPlugins bridge to eslint-plugin-react): covers no-deprecated, prop-types, no-typos, require-default-props, forbid-foreign-prop-types, missing from oxlint's native react plugin. - instructure/no-relative-imports (custom): blocks relative imports that reach into another package's src, bypassing that package's public API. - notice/notice (custom): requires the MIT license header at the top of source files. - vitest/valid-expect (override, ui-truncate-text only): disabled because oxlint's vitest plugin does not recognize the chai-style assertions used in those tests. Nothing consumes oxlint yet. ESLint still runs via lint-staged, ui-scripts lint, and regression-test's lint script.
1 parent 1213044 commit 41a0afe

13 files changed

Lines changed: 1561 additions & 282 deletions

File tree

.oxlintrc.json

Lines changed: 546 additions & 0 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
"lerna": "9.0.7",
107107
"lint-staged": "^17.0.8",
108108
"moment-timezone": "^0.6.2",
109+
"oxlint": "^1.72.0",
109110
"prettier": "2.8.8",
110111
"playwright": "1.61.1",
111112
"react": "18.3.1",

pnpm-lock.yaml

Lines changed: 365 additions & 282 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

regression-test/.oxlintrc.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"$schema": "../node_modules/oxlint/configuration_schema.json",
3+
"extends": ["../.oxlintrc.json"],
4+
"jsPlugins": [
5+
{
6+
"name": "react-js",
7+
"specifier": "eslint-plugin-react"
8+
},
9+
"../scripts/oxlint/no-relative-imports-plugin.mjs",
10+
"../scripts/oxlint/notice-plugin.mjs"
11+
],
12+
"settings": {
13+
"react": {
14+
"version": "19.2.7"
15+
}
16+
},
17+
"ignorePatterns": [
18+
"**/node_modules/**",
19+
"**/.next/**",
20+
"**/next-env.d.ts",
21+
"out/**",
22+
"coverage/**"
23+
]
24+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "@fixtures/pkg-a",
3+
"private": true
4+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2015 - present Instructure, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
import sibling from './sibling'
26+
import other from '../../pkg-b/src/index'
27+
28+
export default { sibling, other }
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2015 - present Instructure, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
export default 'sibling'
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "@fixtures/pkg-b",
3+
"private": true
4+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2015 - present Instructure, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
export default 'pkg-b'
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2015 - present Instructure, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
import { describe, expect, it } from 'vitest'
26+
import path from 'node:path'
27+
import plugin, {
28+
checkImportForRelativePackage,
29+
findNamedPackage
30+
} from '../no-relative-imports-plugin.mjs'
31+
32+
const FIXTURES = path.join(__dirname, '__testfixtures__', 'no-relative-imports')
33+
const PKG_A_INDEX = path.join(FIXTURES, 'pkg-a', 'src', 'index.ts')
34+
35+
describe('no-relative-imports-plugin', () => {
36+
describe('findNamedPackage', () => {
37+
it('walks up from a file to the nearest named package.json', () => {
38+
const found = findNamedPackage(PKG_A_INDEX)
39+
expect(found.packageJson?.name).toBe('@fixtures/pkg-a')
40+
})
41+
})
42+
43+
describe('checkImportForRelativePackage (pure rule logic)', () => {
44+
it('flags a cross-package relative import, with the real error message format', () => {
45+
const message = checkImportForRelativePackage(
46+
'../../pkg-b/src/index',
47+
PKG_A_INDEX
48+
)
49+
expect(message).toBe(
50+
'Relative import from another package is not allowed. ' +
51+
'Use "@fixtures/pkg-b/index" instead of "../../pkg-b/src/index"'
52+
)
53+
})
54+
55+
it('stays silent on a same-package sibling import', () => {
56+
const message = checkImportForRelativePackage('./sibling', PKG_A_INDEX)
57+
expect(message).toBeNull()
58+
})
59+
60+
it('stays silent on a non-relative (bare specifier) import', () => {
61+
const message = checkImportForRelativePackage(
62+
'@instructure/ui-buttons',
63+
PKG_A_INDEX
64+
)
65+
expect(message).toBeNull()
66+
})
67+
68+
it('stays silent when there is no context filename', () => {
69+
const message = checkImportForRelativePackage('../../pkg-b/src/index', '')
70+
expect(message).toBeNull()
71+
})
72+
})
73+
74+
describe('the oxlint rule (create/visitor), independent of the oxlint CLI', () => {
75+
function lintWithVisitor(filename: string, imports: string[]) {
76+
const reports: unknown[] = []
77+
const context = {
78+
filename,
79+
report: (report: unknown) => reports.push(report)
80+
}
81+
const visitor = plugin.rules['no-relative-imports'].create(
82+
context as never
83+
)
84+
for (const source of imports) {
85+
visitor.ImportDeclaration({ source: { value: source } } as never)
86+
}
87+
return reports
88+
}
89+
90+
it('reports exactly one violation for a cross-package import, none for a sibling import', () => {
91+
const reports = lintWithVisitor(PKG_A_INDEX, [
92+
'./sibling',
93+
'../../pkg-b/src/index'
94+
])
95+
expect(reports).toHaveLength(1)
96+
expect((reports[0] as { message: string }).message).toMatch(
97+
/Relative import from another package/
98+
)
99+
})
100+
101+
it('handles CallExpression require() the same way as ImportDeclaration', () => {
102+
const reports: unknown[] = []
103+
const context = {
104+
filename: PKG_A_INDEX,
105+
report: (report: unknown) => reports.push(report)
106+
}
107+
const visitor = plugin.rules['no-relative-imports'].create(
108+
context as never
109+
)
110+
visitor.CallExpression({
111+
callee: { type: 'Identifier', name: 'require' },
112+
arguments: [{ type: 'Literal', value: '../../pkg-b/src/index' }]
113+
} as never)
114+
expect(reports).toHaveLength(1)
115+
})
116+
})
117+
})

0 commit comments

Comments
 (0)