@jeremybarbet/oxlint-plugin 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -18
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -1
- package/dist/rules/no-unnecessary-export.d.mts +3 -0
- package/dist/rules/no-unnecessary-export.mjs +127 -0
- package/dist/rules/no-unnecessary-export.mjs.map +1 -0
- package/dist/rules/no-unused-styles.mjs +9 -3
- package/dist/rules/no-unused-styles.mjs.map +1 -1
- package/package.json +11 -2
package/README.md
CHANGED
|
@@ -35,16 +35,17 @@ pass as every other lint rule — rather than as a batch report from a separate
|
|
|
35
35
|
Not reported:
|
|
36
36
|
|
|
37
37
|
- Exports tagged `/** @public */` or `/** @beta */`, the same opt-out knip honours.
|
|
38
|
-
- Exports consumed inside their own file. Those are over-exposed, not dead
|
|
39
|
-
`export` keyword, not deleting the declaration. Most exported return types look like this
|
|
38
|
+
- Exports consumed inside their own file. Those are over-exposed, not dead: the fix is dropping the
|
|
39
|
+
`export` keyword, not deleting the declaration. Most exported return types look like this, and
|
|
40
|
+
`no-unnecessary-export` below is the rule that reports them.
|
|
40
41
|
- Anything matching `**/*.config.*`, always. A config file is loaded by path by the tool that owns
|
|
41
42
|
it, so its default export is an entry point by definition.
|
|
42
43
|
|
|
43
|
-
| Option
|
|
44
|
-
|
|
|
45
|
-
| `unusedExports`
|
|
46
|
-
| `missingExports`
|
|
47
|
-
| `ignoreExports`
|
|
44
|
+
| Option | Default | |
|
|
45
|
+
| ---------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
46
|
+
| `unusedExports` | `true` | Report exports nothing imports. |
|
|
47
|
+
| `missingExports` | `false` | Report files with no exports. Noisy — every spec and mock qualifies. |
|
|
48
|
+
| `ignoreExports` | `[]` | Globs whose exports are never reported. Put your entry points here. |
|
|
48
49
|
| `ignoreDefaultExports` | `false` | Never report `export default`. Pass globs to scope it — a file-based router like expo-router loads its routes by path, so their default export is an entry point, never an import. |
|
|
49
50
|
|
|
50
51
|
```ts
|
|
@@ -57,12 +58,44 @@ Not reported:
|
|
|
57
58
|
`ignoreDefaultExports` is the narrow version of `ignoreExports`: listing `app/**` there would also
|
|
58
59
|
silence the named exports in those files, so a route that grew a dead helper would go unreported.
|
|
59
60
|
|
|
61
|
+
### `no-unnecessary-export`
|
|
62
|
+
|
|
63
|
+
Reports a binding that is exported but only ever used inside its own declaring file. The
|
|
64
|
+
complement of `no-unused-modules`, which deliberately leaves this case alone because the two want
|
|
65
|
+
opposite fixes:
|
|
66
|
+
|
|
67
|
+
| Rule | Condition | Fix |
|
|
68
|
+
| ----------------------- | ---------------------------------------- | -------------------------- |
|
|
69
|
+
| `no-unused-modules` | Nothing references the export, anywhere. | Delete the declaration. |
|
|
70
|
+
| `no-unnecessary-export` | Only the declaring file references it. | Drop the `export` keyword. |
|
|
71
|
+
|
|
72
|
+
The two never both fire on one name. Together they mean an export is either imported somewhere or
|
|
73
|
+
reported.
|
|
74
|
+
|
|
75
|
+
Type-only exports are skipped by default: an exported interface used only as a local return type is
|
|
76
|
+
the ordinary shape of a typed codebase, not over-exposure. `@public` / `@beta` opt out, same as
|
|
77
|
+
`no-unused-modules`.
|
|
78
|
+
|
|
79
|
+
Reported, never autofixed. The verdict comes from a static crawl that cannot see a runtime-resolved
|
|
80
|
+
import, so the fix is offered as an editor suggestion and `--fix` leaves the file alone. Only a lone
|
|
81
|
+
`export const x = …` gets one: a specifier list drops an entry rather than the keyword, and a
|
|
82
|
+
destructured or multi-declarator export may still have an imported sibling.
|
|
83
|
+
|
|
84
|
+
| Option | Default | |
|
|
85
|
+
| --------------- | ------- | --------------------------------------- |
|
|
86
|
+
| `includeTypes` | `false` | Also report type-only exports. |
|
|
87
|
+
| `ignoreExports` | `[]` | Globs whose exports are never reported. |
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
'op/no-unnecessary-export': 'error',
|
|
91
|
+
```
|
|
92
|
+
|
|
60
93
|
### `no-unused-class-members`
|
|
61
94
|
|
|
62
95
|
Reports public class members that nothing outside the declaring file references.
|
|
63
96
|
|
|
64
97
|
This is the gap that motivated the package. `knip` analyses module-level exports only, so an
|
|
65
|
-
exported class that
|
|
98
|
+
exported class that _is_ used hides its entire body from it; `tsc`'s `noUnusedLocals` covers
|
|
66
99
|
`private` members alone. In a codebase where most logic lives on service and controller classes,
|
|
67
100
|
that leaves most of the code unchecked.
|
|
68
101
|
|
|
@@ -79,11 +112,11 @@ Not reported:
|
|
|
79
112
|
polymorphically by a base class or by whatever holds the interface.
|
|
80
113
|
- Members referenced only from test files (mirrors `knip --production`).
|
|
81
114
|
|
|
82
|
-
| Option
|
|
83
|
-
|
|
|
84
|
-
| `ignoreNames`
|
|
85
|
-
| `ignoreUsagesIn`
|
|
86
|
-
| `reportPrivateCandidates` | `false`
|
|
115
|
+
| Option | Default | |
|
|
116
|
+
| ------------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
117
|
+
| `ignoreNames` | framework lifecycle hooks | Member names never reported. |
|
|
118
|
+
| `ignoreUsagesIn` | specs, mocks, `src/test/**` | Globs whose references do not count as usage. |
|
|
119
|
+
| `reportPrivateCandidates` | `false` | Also report members used _only_ inside their own file, suggesting `private`. Off by default: those are a refactor prompt, not dead code. |
|
|
87
120
|
|
|
88
121
|
### `no-export-all`
|
|
89
122
|
|
|
@@ -92,7 +125,8 @@ Disallows `export *`, which defeats tree-shaking and invites name clashes.
|
|
|
92
125
|
### `no-unused-styles`
|
|
93
126
|
|
|
94
127
|
React Native. Reports entries in a `StyleSheet.create` object that nothing consumes. Understands the
|
|
95
|
-
plain object form and both function forms `react-native-unistyles` uses
|
|
128
|
+
plain object form and both function forms `react-native-unistyles` uses, and counts reads of the styles
|
|
129
|
+
`useVariants` returns.
|
|
96
130
|
|
|
97
131
|
### `require-cache-control`
|
|
98
132
|
|
|
@@ -101,10 +135,10 @@ nested type cannot silently break Apollo's cache-control computation. Ships an a
|
|
|
101
135
|
|
|
102
136
|
## Project discovery
|
|
103
137
|
|
|
104
|
-
`no-unused-modules` and `no-unused-class-members` need a whole-project
|
|
105
|
-
the project once per process and answer per-file from the result.
|
|
106
|
-
with no end-of-run hook, so this is the same approach
|
|
107
|
-
120ms on an 830-file repo.
|
|
138
|
+
`no-unused-modules`, `no-unnecessary-export` and `no-unused-class-members` need a whole-project
|
|
139
|
+
view, so they crawl and parse the project once per process and answer per-file from the result.
|
|
140
|
+
(Oxlint's plugin API is per-file with no end-of-run hook, so this is the same approach
|
|
141
|
+
`eslint-plugin-import` takes.) It costs roughly 120ms on an 830-file repo.
|
|
108
142
|
|
|
109
143
|
Discovery is zero-config: everything under the oxlint working directory that looks like source,
|
|
110
144
|
skipping `node_modules`, `dist`, `build`, `coverage`, `android`, `ios` and dotfile directories.
|
package/dist/index.d.mts
CHANGED
|
@@ -12,6 +12,7 @@ declare const _default: {
|
|
|
12
12
|
};
|
|
13
13
|
rules: {
|
|
14
14
|
'no-export-all': import("./types.mts").Rule;
|
|
15
|
+
'no-unnecessary-export': import("./types.mts").Rule;
|
|
15
16
|
'no-unused-class-members': import("./types.mts").Rule;
|
|
16
17
|
'no-unused-modules': import("./types.mts").Rule;
|
|
17
18
|
'no-unused-styles': import("./types.mts").Rule;
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import noExportAll from "./rules/no-export-all.mjs";
|
|
2
|
+
import noUnnecessaryExport from "./rules/no-unnecessary-export.mjs";
|
|
2
3
|
import noUnusedClassMembers from "./rules/no-unused-class-members.mjs";
|
|
3
4
|
import noUnusedModules from "./rules/no-unused-modules.mjs";
|
|
4
5
|
import noUnusedStyles from "./rules/no-unused-styles.mjs";
|
|
@@ -15,6 +16,7 @@ export default {
|
|
|
15
16
|
meta: { name: 'op' },
|
|
16
17
|
rules: {
|
|
17
18
|
'no-export-all': noExportAll,
|
|
19
|
+
'no-unnecessary-export': noUnnecessaryExport,
|
|
18
20
|
'no-unused-class-members': noUnusedClassMembers,
|
|
19
21
|
'no-unused-modules': noUnusedModules,
|
|
20
22
|
'no-unused-styles': noUnusedStyles,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.mts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,2BAA2B,CAAC;AACpD,OAAO,oBAAoB,MAAM,qCAAqC,CAAC;AACvE,OAAO,eAAe,MAAM,+BAA+B,CAAC;AAC5D,OAAO,cAAc,MAAM,8BAA8B,CAAC;AAC1D,OAAO,mBAAmB,MAAM,mCAAmC,CAAC;AAEpE;;;;;;;GAOG;AACH,eAAe;IACb,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;IACpB,KAAK,EAAE;QACL,eAAe,EAAE,WAAW;QAC5B,yBAAyB,EAAE,oBAAoB;QAC/C,mBAAmB,EAAE,eAAe;QACpC,kBAAkB,EAAE,cAAc;QAClC,uBAAuB,EAAE,mBAAmB;KAC7C;CACF,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.mts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,2BAA2B,CAAC;AACpD,OAAO,mBAAmB,MAAM,mCAAmC,CAAC;AACpE,OAAO,oBAAoB,MAAM,qCAAqC,CAAC;AACvE,OAAO,eAAe,MAAM,+BAA+B,CAAC;AAC5D,OAAO,cAAc,MAAM,8BAA8B,CAAC;AAC1D,OAAO,mBAAmB,MAAM,mCAAmC,CAAC;AAEpE;;;;;;;GAOG;AACH,eAAe;IACb,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;IACpB,KAAK,EAAE;QACL,eAAe,EAAE,WAAW;QAC5B,uBAAuB,EAAE,mBAAmB;QAC5C,yBAAyB,EAAE,oBAAoB;QAC/C,mBAAmB,EAAE,eAAe;QACpC,kBAAkB,EAAE,cAAc;QAClC,uBAAuB,EAAE,mBAAmB;KAC7C;CACF,CAAC"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { relative } from 'node:path';
|
|
2
|
+
import { matchesGlob } from "../glob.mjs";
|
|
3
|
+
import { getProjectIndex, readProjectSettings } from "../project-index.mjs";
|
|
4
|
+
import {} from "../types.mjs";
|
|
5
|
+
/**
|
|
6
|
+
* The complement of `no-unused-modules`: that rule treats an export its own file consumes as used,
|
|
7
|
+
* which leaves an over-exposed binding unreported. Nothing imports it, so the `export` should go.
|
|
8
|
+
*/
|
|
9
|
+
/** Always applied, on top of whatever a project configures. Mirrors `no-unused-modules`. */
|
|
10
|
+
const ALWAYS_IGNORED_EXPORTS = ['**/*.config.*'];
|
|
11
|
+
/** Declarations that emit no runtime binding, so exporting one is not the over-exposure this rule means. */
|
|
12
|
+
const TYPE_DECLARATIONS = new Set(['TSInterfaceDeclaration', 'TSTypeAliasDeclaration']);
|
|
13
|
+
const rule = {
|
|
14
|
+
meta: {
|
|
15
|
+
type: 'problem',
|
|
16
|
+
docs: {
|
|
17
|
+
description: 'Disallow exporting a binding only its own file uses',
|
|
18
|
+
},
|
|
19
|
+
messages: {
|
|
20
|
+
unnecessary: "'{{name}}' is only used in this file. Drop the `export` keyword.",
|
|
21
|
+
},
|
|
22
|
+
schema: [
|
|
23
|
+
{
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
includeTypes: { type: 'boolean' },
|
|
27
|
+
ignoreExports: { type: 'array', items: { type: 'string' } },
|
|
28
|
+
},
|
|
29
|
+
additionalProperties: false,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
hasSuggestions: true,
|
|
33
|
+
},
|
|
34
|
+
create(context) {
|
|
35
|
+
const raw = (context.options?.[0] ?? {});
|
|
36
|
+
const includeTypes = raw.includeTypes ?? false;
|
|
37
|
+
const ignoreExports = [...ALWAYS_IGNORED_EXPORTS, ...(raw.ignoreExports ?? [])];
|
|
38
|
+
const index = getProjectIndex(context.cwd, readProjectSettings(context));
|
|
39
|
+
const self = context.filename;
|
|
40
|
+
const file = index.files.get(self);
|
|
41
|
+
if (!file) {
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
44
|
+
const relativePath = relative(index.root, self).split('\\').join('/');
|
|
45
|
+
if (matchesGlob(relativePath, ignoreExports)) {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
const importers = index.importedFrom.get(self);
|
|
49
|
+
// A namespace import (`import * as x`) reaches every export, so none of them can be called local.
|
|
50
|
+
if (importers?.has('*')) {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
const isLocalOnly = (name) => {
|
|
54
|
+
if (importers?.has(name) || file.intentionalExports.has(name)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
// The inversion. Without a local reference the export is dead, which is `no-unused-modules`'
|
|
58
|
+
// finding, not this one, and the two must never both fire on a name.
|
|
59
|
+
return file.localReferences.has(name);
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* The span covering the `export` keyword, or null when removing it is not the right fix. A
|
|
63
|
+
* specifier list drops one entry instead, and a destructured export may have an imported sibling.
|
|
64
|
+
*/
|
|
65
|
+
const exportKeywordRange = (node, declaration) => {
|
|
66
|
+
if ((node.specifiers ?? []).length > 0) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const declarations = (declaration.declarations ?? []);
|
|
70
|
+
const single = declaration.id?.type === 'Identifier' ||
|
|
71
|
+
(declarations.length === 1 && declarations[0]?.id?.type === 'Identifier');
|
|
72
|
+
return single ? [node.range[0], declaration.range[0]] : null;
|
|
73
|
+
};
|
|
74
|
+
const report = (node, name, range) => {
|
|
75
|
+
if (!isLocalOnly(name)) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
context.report({
|
|
79
|
+
node,
|
|
80
|
+
messageId: 'unnecessary',
|
|
81
|
+
data: { name },
|
|
82
|
+
...(range && {
|
|
83
|
+
suggest: [
|
|
84
|
+
{
|
|
85
|
+
desc: 'Remove the `export` keyword.',
|
|
86
|
+
fix: fixer => fixer.removeRange(range),
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
}),
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
return {
|
|
93
|
+
/**
|
|
94
|
+
* `export default` is out of scope: its export name is `default`, a reserved word no
|
|
95
|
+
* identifier can match, so it never has a local reference to begin with.
|
|
96
|
+
*/
|
|
97
|
+
ExportNamedDeclaration(node) {
|
|
98
|
+
if (node.source || (!includeTypes && node.exportKind === 'type')) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
for (const specifier of (node.specifiers ?? [])) {
|
|
102
|
+
const name = specifier.exported?.name;
|
|
103
|
+
if (!name || name === 'default' || (!includeTypes && specifier.exportKind === 'type')) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
report(specifier.exported, name, null);
|
|
107
|
+
}
|
|
108
|
+
const declaration = node.declaration;
|
|
109
|
+
if (!declaration || (!includeTypes && TYPE_DECLARATIONS.has(declaration.type))) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const range = exportKeywordRange(node, declaration);
|
|
113
|
+
if (declaration.id?.name) {
|
|
114
|
+
report(declaration.id, declaration.id.name, range);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
for (const declarator of (declaration.declarations ?? [])) {
|
|
118
|
+
if (declarator.id?.type === 'Identifier') {
|
|
119
|
+
report(declarator.id, declarator.id.name, range);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
export default rule;
|
|
127
|
+
//# sourceMappingURL=no-unnecessary-export.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-unnecessary-export.mjs","sourceRoot":"","sources":["../../src/rules/no-unnecessary-export.mts"],"names":[],"mappings":"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"}
|
|
@@ -46,6 +46,7 @@ const rule = {
|
|
|
46
46
|
create(context) {
|
|
47
47
|
const styleSheets = new Map();
|
|
48
48
|
const usages = [];
|
|
49
|
+
const aliases = new Map();
|
|
49
50
|
return {
|
|
50
51
|
// Collect every member expression up front — the stylesheet it refers to may not have been
|
|
51
52
|
// visited yet, so matching is deferred to `Program:exit`.
|
|
@@ -78,6 +79,10 @@ const rule = {
|
|
|
78
79
|
styleSheets.set(variableName, { node: stylesNode, styles: new Set(styles) });
|
|
79
80
|
}
|
|
80
81
|
},
|
|
82
|
+
// `const variantStyles = styles.useVariants({ size })`
|
|
83
|
+
'VariableDeclarator[id.type="Identifier"][init.callee.object.type="Identifier"][init.callee.property.name="useVariants"]'(node) {
|
|
84
|
+
aliases.set(node.id.name, node.init.callee.object.name);
|
|
85
|
+
},
|
|
81
86
|
// `const { container } = styles`
|
|
82
87
|
VariableDeclarator(node) {
|
|
83
88
|
if (node.init?.type !== 'Identifier' || node.id.type !== 'ObjectPattern') {
|
|
@@ -99,17 +104,18 @@ const rule = {
|
|
|
99
104
|
'Program:exit'() {
|
|
100
105
|
const usedStyles = new Set();
|
|
101
106
|
for (const { objectName, styleName } of usages) {
|
|
102
|
-
const
|
|
107
|
+
const sheetName = aliases.get(objectName) ?? objectName;
|
|
108
|
+
const sheet = styleSheets.get(sheetName);
|
|
103
109
|
if (!sheet) {
|
|
104
110
|
continue;
|
|
105
111
|
}
|
|
106
112
|
if (styleName === ALL_STYLES) {
|
|
107
113
|
for (const name of sheet.styles) {
|
|
108
|
-
usedStyles.add(`${
|
|
114
|
+
usedStyles.add(`${sheetName}.${name}`);
|
|
109
115
|
}
|
|
110
116
|
}
|
|
111
117
|
else {
|
|
112
|
-
usedStyles.add(`${
|
|
118
|
+
usedStyles.add(`${sheetName}.${styleName}`);
|
|
113
119
|
}
|
|
114
120
|
}
|
|
115
121
|
for (const [sheetName, sheet] of styleSheets) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-unused-styles.mjs","sourceRoot":"","sources":["../../src/rules/no-unused-styles.mts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,MAAM,cAAc,CAAC;AAYlE,kEAAkE;AAClE,MAAM,UAAU,GAAG,GAAG,CAAC;AAEvB,MAAM,aAAa,GAAG,CAAC,MAAY,EAAY,EAAE,CAC9C,MAAM,CAAC,UAAqB;KAC1B,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,UAAU,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,KAAK,YAAY,CAAC;KAC3E,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAc,CAAC,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,eAAe,GAAG,CAAC,QAA0B,EAAe,EAAE;IAClE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,QAAQ,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;QACzC,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,IAAI,QAAQ,CAAC,IAAI,KAAK,yBAAyB,EAAE,CAAC;QAChD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;QAC9C,OAAO,QAAQ,CAAC,IAAY,CAAC;IAC/B,CAAC;IAED,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,gBAAgB,EAAE,CAAC;QAC5C,MAAM,eAAe,GAAI,QAAQ,CAAC,IAAI,CAAC,IAAe,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,iBAAiB,CAAC,CAAC;QAE/G,IAAI,eAAe,EAAE,QAAQ,EAAE,IAAI,KAAK,kBAAkB,EAAE,CAAC;YAC3D,OAAO,eAAe,CAAC,QAAgB,CAAC;QAC1C,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,MAAM,IAAI,GAAS;IACjB,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,8EAA8E;SAC5F;QACD,QAAQ,EAAE;YACR,WAAW,EAAE,6CAA6C;SAC3D;QACD,MAAM,EAAE,EAAE;QACV,OAAO,EAAE,MAAM;KAChB;IACD,MAAM,CAAC,OAAgB;QACrB,MAAM,WAAW,GAAG,IAAI,GAAG,EAA2B,CAAC;QACvD,MAAM,MAAM,GAAY,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"no-unused-styles.mjs","sourceRoot":"","sources":["../../src/rules/no-unused-styles.mts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeremybarbet/oxlint-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Shared oxlint JS plugin: dead-code rules that oxlint, tsc and knip each miss, plus a few house rules.",
|
|
5
5
|
"author": "Jérémy Barbet <jeremgraph@gmail.com> (https://github.com/jeremybarbet)",
|
|
6
6
|
"repository": "https://github.com/jeremybarbet/oxlint-plugin",
|
|
7
7
|
"homepage": "https://github.com/jeremybarbet/oxlint-plugin#readme",
|
|
8
8
|
"license": "MIT",
|
|
9
|
-
"packageManager": "yarn@4.
|
|
9
|
+
"packageManager": "yarn@4.18.0",
|
|
10
10
|
"engines": {
|
|
11
11
|
"node": ">=22"
|
|
12
12
|
},
|
|
@@ -39,6 +39,12 @@
|
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "rm -rf dist && tsc",
|
|
42
|
+
"lint": "yarn lint:tsc && yarn lint:oxlint && yarn lint:oxfmt",
|
|
43
|
+
"lint:ci": "yarn lint:tsc && yarn lint:oxlint:ci && yarn lint:oxfmt:ci",
|
|
44
|
+
"lint:oxfmt": "oxfmt -c oxfmt.config.mts",
|
|
45
|
+
"lint:oxfmt:ci": "oxfmt -c oxfmt.config.mts --check",
|
|
46
|
+
"lint:oxlint": "oxlint -c oxlint.config.mts --fix",
|
|
47
|
+
"lint:oxlint:ci": "oxlint -c oxlint.config.mts",
|
|
42
48
|
"lint:tsc": "tsc --noEmit",
|
|
43
49
|
"prepack": "yarn build"
|
|
44
50
|
},
|
|
@@ -50,6 +56,9 @@
|
|
|
50
56
|
},
|
|
51
57
|
"devDependencies": {
|
|
52
58
|
"@types/node": "25.9.1",
|
|
59
|
+
"oxfmt": "0.65.0",
|
|
60
|
+
"oxlint": "1.80.0",
|
|
61
|
+
"oxlint-tsgolint": "7.0.2001",
|
|
53
62
|
"typescript": "5.9.3"
|
|
54
63
|
}
|
|
55
64
|
}
|