oxlint-plugin-ts-no-assert 0.1.1
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 +77 -0
- package/dist/index.js +180 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# oxlint-plugin-ts-no-assert
|
|
2
|
+
|
|
3
|
+
Disallow TypeScript type assertions (`as`, angle-bracket `<Type>`, and non-null `!`) — ported from [eslint-plugin-no-type-assertion](https://github.com/Dremora/eslint-plugin-no-type-assertion) to [Oxlint's JS plugin API](https://oxc.rs/docs/guide/usage/linter/plugins). Also works with ESLint flat config via the bundled `eslintCompatPlugin` wrapper.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Oxlint `>= 1.0.0` (requires `jsPlugins` support)
|
|
8
|
+
- Node.js `>= 18`
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm add -D oxlint-plugin-ts-no-assert
|
|
14
|
+
# or
|
|
15
|
+
bun add -d oxlint-plugin-ts-no-assert
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage with Oxlint
|
|
19
|
+
|
|
20
|
+
Add the plugin to `jsPlugins` and enable the rule:
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
// .oxlintrc.json
|
|
24
|
+
{
|
|
25
|
+
"jsPlugins": ["./node_modules/oxlint-plugin-ts-no-assert/dist/index.js"],
|
|
26
|
+
"rules": {
|
|
27
|
+
"oxlint-plugin-ts-no-assert/no-type-assertion": "error"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage with ESLint
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
// eslint.config.mjs
|
|
36
|
+
import tsNoAssertPlugin from "oxlint-plugin-ts-no-assert/dist/index.js";
|
|
37
|
+
|
|
38
|
+
export default [
|
|
39
|
+
{
|
|
40
|
+
plugins: { "no-type-assertion": tsNoAssertPlugin },
|
|
41
|
+
rules: tsNoAssertPlugin.configs.recommended.rules,
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Rules
|
|
47
|
+
|
|
48
|
+
### `no-type-assertion/no-type-assertion`
|
|
49
|
+
|
|
50
|
+
Disallows all three forms of TypeScript type assertion.
|
|
51
|
+
|
|
52
|
+
**Forbidden:**
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
const x = value as MyType; // ❌ Do not use `as` operator for type assertion
|
|
56
|
+
const x = <MyType>value; // ❌ Do not use type assertion (angle-bracket)
|
|
57
|
+
const x = maybeNull!; // ❌ Do not use non-null assertion operator
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Allowed** — safe widening that TypeScript itself endorses:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
const x = value as unknown; // ✅ widening to unknown is safe
|
|
64
|
+
const x = value as const; // ✅ const assertion
|
|
65
|
+
const x = <unknown>value; // ✅
|
|
66
|
+
const x = <const>value; // ✅
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
> **Note on angle-bracket syntax:** TypeScript disallows `<Type>value` in `.tsx` files because it is syntactically ambiguous with JSX. The `angleBracketAssertion` diagnostic will only appear in plain `.ts` files.
|
|
70
|
+
|
|
71
|
+
| Rule | Description | Recommended |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `no-type-assertion` | Disallow `as`, `<Type>`, and `!` type assertions (except `as const` / `as unknown`) | error |
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// node_modules/.bun/@oxlint+plugins@1.43.0/node_modules/@oxlint/plugins/index.js
|
|
2
|
+
var EMPTY_VISITOR = {};
|
|
3
|
+
function eslintCompatPlugin(plugin) {
|
|
4
|
+
if (typeof plugin != "object" || !plugin)
|
|
5
|
+
throw Error("Plugin must be an object");
|
|
6
|
+
let { rules } = plugin;
|
|
7
|
+
if (typeof rules != "object" || !rules)
|
|
8
|
+
throw Error("Plugin must have an object as `rules` property");
|
|
9
|
+
for (let ruleName in rules)
|
|
10
|
+
Object.hasOwn(rules, ruleName) && convertRule(rules[ruleName]);
|
|
11
|
+
return plugin;
|
|
12
|
+
}
|
|
13
|
+
function convertRule(rule) {
|
|
14
|
+
if (typeof rule != "object" || !rule)
|
|
15
|
+
throw Error("Rule must be an object");
|
|
16
|
+
if ("create" in rule)
|
|
17
|
+
return;
|
|
18
|
+
let context = null, visitor, beforeHook;
|
|
19
|
+
rule.create = (eslintContext) => (context === null && ({ context, visitor, beforeHook } = createContextAndVisitor(rule)), Object.defineProperties(context, {
|
|
20
|
+
id: { value: eslintContext.id },
|
|
21
|
+
options: { value: eslintContext.options },
|
|
22
|
+
report: { value: eslintContext.report }
|
|
23
|
+
}), Object.setPrototypeOf(context, Object.getPrototypeOf(eslintContext)), beforeHook !== null && beforeHook() === false ? EMPTY_VISITOR : visitor);
|
|
24
|
+
}
|
|
25
|
+
var FILE_CONTEXT = Object.freeze({
|
|
26
|
+
get filename() {
|
|
27
|
+
throw Error("Cannot access `context.filename` in `createOnce`");
|
|
28
|
+
},
|
|
29
|
+
getFilename() {
|
|
30
|
+
throw Error("Cannot call `context.getFilename` in `createOnce`");
|
|
31
|
+
},
|
|
32
|
+
get physicalFilename() {
|
|
33
|
+
throw Error("Cannot access `context.physicalFilename` in `createOnce`");
|
|
34
|
+
},
|
|
35
|
+
getPhysicalFilename() {
|
|
36
|
+
throw Error("Cannot call `context.getPhysicalFilename` in `createOnce`");
|
|
37
|
+
},
|
|
38
|
+
get cwd() {
|
|
39
|
+
throw Error("Cannot access `context.cwd` in `createOnce`");
|
|
40
|
+
},
|
|
41
|
+
getCwd() {
|
|
42
|
+
throw Error("Cannot call `context.getCwd` in `createOnce`");
|
|
43
|
+
},
|
|
44
|
+
get sourceCode() {
|
|
45
|
+
throw Error("Cannot access `context.sourceCode` in `createOnce`");
|
|
46
|
+
},
|
|
47
|
+
getSourceCode() {
|
|
48
|
+
throw Error("Cannot call `context.getSourceCode` in `createOnce`");
|
|
49
|
+
},
|
|
50
|
+
get languageOptions() {
|
|
51
|
+
throw Error("Cannot access `context.languageOptions` in `createOnce`");
|
|
52
|
+
},
|
|
53
|
+
get settings() {
|
|
54
|
+
throw Error("Cannot access `context.settings` in `createOnce`");
|
|
55
|
+
},
|
|
56
|
+
extend(extension) {
|
|
57
|
+
return Object.freeze(Object.assign(Object.create(this), extension));
|
|
58
|
+
},
|
|
59
|
+
get parserOptions() {
|
|
60
|
+
throw Error("Cannot access `context.parserOptions` in `createOnce`");
|
|
61
|
+
},
|
|
62
|
+
get parserPath() {
|
|
63
|
+
throw Error("Cannot access `context.parserPath` in `createOnce`");
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
function createContextAndVisitor(rule) {
|
|
67
|
+
let { createOnce } = rule;
|
|
68
|
+
if (createOnce == null)
|
|
69
|
+
throw Error("Rules must define either a `create` or `createOnce` method");
|
|
70
|
+
if (typeof createOnce != "function")
|
|
71
|
+
throw Error("Rule `createOnce` property must be a function");
|
|
72
|
+
let context = Object.create(FILE_CONTEXT, {
|
|
73
|
+
id: {
|
|
74
|
+
value: "",
|
|
75
|
+
enumerable: true,
|
|
76
|
+
configurable: true
|
|
77
|
+
},
|
|
78
|
+
options: {
|
|
79
|
+
value: null,
|
|
80
|
+
enumerable: true,
|
|
81
|
+
configurable: true
|
|
82
|
+
},
|
|
83
|
+
report: {
|
|
84
|
+
value: null,
|
|
85
|
+
enumerable: true,
|
|
86
|
+
configurable: true
|
|
87
|
+
}
|
|
88
|
+
}), { before: beforeHook, after: afterHook, ...visitor } = createOnce.call(rule, context);
|
|
89
|
+
if (beforeHook === undefined)
|
|
90
|
+
beforeHook = null;
|
|
91
|
+
else if (beforeHook !== null && typeof beforeHook != "function")
|
|
92
|
+
throw Error("`before` property of visitor must be a function if defined");
|
|
93
|
+
if (afterHook != null) {
|
|
94
|
+
if (typeof afterHook != "function")
|
|
95
|
+
throw Error("`after` property of visitor must be a function if defined");
|
|
96
|
+
let maxAttrs = -1;
|
|
97
|
+
for (let key2 in visitor) {
|
|
98
|
+
if (!Object.hasOwn(visitor, key2) || !key2.endsWith(":exit"))
|
|
99
|
+
continue;
|
|
100
|
+
let end = key2.length - 5, count = 0;
|
|
101
|
+
for (let i = 0;i < end; i++) {
|
|
102
|
+
let c = key2.charCodeAt(i);
|
|
103
|
+
(c === 91 || c === 46 || c === 58) && count++;
|
|
104
|
+
}
|
|
105
|
+
count > maxAttrs && (maxAttrs = count);
|
|
106
|
+
}
|
|
107
|
+
let key = `Program${"[type]".repeat(maxAttrs + 1)}:exit`;
|
|
108
|
+
visitor[key] = (_node) => afterHook();
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
context,
|
|
112
|
+
visitor,
|
|
113
|
+
beforeHook
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// packages/plugin-no-type-assertion/src/rules/no-type-assertion.ts
|
|
118
|
+
function isAllowedTypeAnnotation(typeAnnotation) {
|
|
119
|
+
if (typeAnnotation.type === "TSUnknownKeyword") {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
if (typeAnnotation.type === "TSTypeReference" && typeAnnotation.typeName?.type === "Identifier" && typeAnnotation.typeName?.name === "const") {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
var no_type_assertion_default = {
|
|
128
|
+
meta: {
|
|
129
|
+
type: "suggestion",
|
|
130
|
+
docs: {
|
|
131
|
+
description: "Disallow type assertions in TypeScript code.",
|
|
132
|
+
recommended: "error"
|
|
133
|
+
},
|
|
134
|
+
messages: {
|
|
135
|
+
angleBracketAssertion: "Do not use type assertion",
|
|
136
|
+
asAssertion: "Do not use `as` operator for type assertion",
|
|
137
|
+
nonNullAssertion: "Do not use non-null assertion operator"
|
|
138
|
+
},
|
|
139
|
+
schema: []
|
|
140
|
+
},
|
|
141
|
+
defaultOptions: [],
|
|
142
|
+
createOnce(context) {
|
|
143
|
+
return {
|
|
144
|
+
TSTypeAssertion(node) {
|
|
145
|
+
if (isAllowedTypeAnnotation(node.typeAnnotation)) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
context.report({ node, messageId: "angleBracketAssertion" });
|
|
149
|
+
},
|
|
150
|
+
TSAsExpression(node) {
|
|
151
|
+
if (isAllowedTypeAnnotation(node.typeAnnotation)) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
context.report({ node, messageId: "asAssertion" });
|
|
155
|
+
},
|
|
156
|
+
TSNonNullExpression(node) {
|
|
157
|
+
context.report({ node, messageId: "nonNullAssertion" });
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// packages/plugin-no-type-assertion/src/index.ts
|
|
164
|
+
var plugin = eslintCompatPlugin({
|
|
165
|
+
meta: { name: "oxlint-plugin-ts-no-assert" },
|
|
166
|
+
rules: {
|
|
167
|
+
"no-type-assertion": no_type_assertion_default
|
|
168
|
+
},
|
|
169
|
+
configs: {
|
|
170
|
+
recommended: {
|
|
171
|
+
rules: {
|
|
172
|
+
"no-type-assertion/no-type-assertion": "error"
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
var src_default = plugin;
|
|
178
|
+
export {
|
|
179
|
+
src_default as default
|
|
180
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "oxlint-plugin-ts-no-assert",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Disallow TypeScript type assertions (as, angle-bracket, non-null !) — ported from eslint-plugin-no-type-assertion to Oxlint's JS plugin API. Also compatible with ESLint flat config.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"oxlint",
|
|
19
|
+
"eslint",
|
|
20
|
+
"typescript",
|
|
21
|
+
"type-assertion",
|
|
22
|
+
"non-null-assertion",
|
|
23
|
+
"lint"
|
|
24
|
+
],
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/takinprofit/biome-plugins"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18.0.0"
|
|
31
|
+
}
|
|
32
|
+
}
|