oxlint-plugin-deslopify 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Brian Estrada
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # oxlint-plugin-deslopify
2
+
3
+ [Oxlint](https://oxc.rs/docs/guide/usage/linter) rules that keep AI agents from writing sloppy code.
4
+
5
+ Agents follow a linter more reliably than a style guide in a prompt. This plugin turns the patterns they reach for (multi-line ternaries, one-line objects, `T["key"]` type lookups, unchecked casts) into lint errors, and ships a preset that pairs them with Oxlint's built-in rules for the same problems.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ bun add -d oxlint oxlint-plugin-deslopify
11
+ # For the type-aware rules in the preset:
12
+ bun add -d oxlint-tsgolint
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ Extend the recommended preset in `oxlint.config.ts`:
18
+
19
+ ```ts
20
+ import { defineConfig } from "oxlint";
21
+ import deslopify from "oxlint-plugin-deslopify";
22
+
23
+ export default defineConfig({
24
+ extends: [deslopify.configs.recommended],
25
+ options: {
26
+ // Without this, the preset's type-aware rules are skipped.
27
+ typeAware: true,
28
+ },
29
+ });
30
+ ```
31
+
32
+ Or load the plugin and pick rules yourself, in `oxlint.config.ts` or `.oxlintrc.json`:
33
+
34
+ ```json
35
+ {
36
+ "jsPlugins": ["oxlint-plugin-deslopify"],
37
+ "rules": {
38
+ "deslopify/simple-ternary": "error"
39
+ }
40
+ }
41
+ ```
42
+
43
+ ## Rules
44
+
45
+ โœ… in `configs.recommended` ยท ๐Ÿ”ง fixable with `--fix`
46
+
47
+ | Rule | Description | โœ… | ๐Ÿ”ง |
48
+ | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------- | --- | --- |
49
+ | [`multiline-object`](docs/rules/multiline-object.md) | Put each property of an object literal with two or more properties on its own line. | โœ… | ๐Ÿ”ง |
50
+ | [`no-graphql-cast`](docs/rules/no-graphql-cast.md) | Require `graphql<unknown>` so GraphQL responses are parsed, not cast. | | |
51
+ | [`no-property-type-lookup`](docs/rules/no-property-type-lookup.md) | Disallow looking up a property's type with `T["key"]`; name the type instead. | โœ… | |
52
+ | [`simple-ternary`](docs/rules/simple-ternary.md) | Keep ternaries on one line, testing one condition, without interpolated strings. | โœ… | |
53
+
54
+ ## What the preset turns on
55
+
56
+ Besides the โœ… rules above, `configs.recommended` sets these built-in Oxlint rules to `"error"`:
57
+
58
+ | Rule | Setting | Why |
59
+ | ---------------------------------------- | ------------- | ---------------------------------------------------------------------------- |
60
+ | `func-style` | `declaration` | Named functions are `function` declarations, not arrows assigned to a const. |
61
+ | `object-shorthand` | `never` | `{ key: key }` and `key: function () {}`, never the shorthand. |
62
+ | `curly` | `all` | Braces on every block body, so extending one is a clean diff. |
63
+ | `arrow-body-style` | `always` | Arrow functions get a block body and an explicit `return`. |
64
+ | `typescript/consistent-type-definitions` | `interface` | Object types are interfaces, not type aliases. |
65
+ | `typescript/no-deprecated` | type-aware | Flags `@deprecated` APIs, which `tsc` never reports. |
66
+ | `typescript/no-unsafe-type-assertion` | type-aware | Unchecked casts let unvalidated data through. |
67
+ | `typescript/no-unsafe-assignment` | type-aware | Stops `any` spreading into typed variables. |
68
+ | `typescript/no-unsafe-member-access` | type-aware | Stops property access on `any`. |
69
+ | `typescript/no-unsafe-argument` | type-aware | Stops `any` being passed as a typed argument. |
70
+
71
+ Override any of them in your own `rules` after `extends`.
72
+
73
+ ## Requirements
74
+
75
+ - Oxlint 1.86.0 or later. Oxlint's JS plugin support is still in alpha and not covered by semver.
76
+ - Node.js `^20.19.0` or `>=22.12.0`.
77
+ - `multiline-object`'s fix only adds a newline after `{`. Run [oxfmt](https://oxc.rs/docs/guide/usage/formatter) (or Prettier) with the default `objectWrap: "preserve"` to lay out the rest.
78
+
79
+ ## Development
80
+
81
+ ```sh
82
+ bun install
83
+ bun run check
84
+ ```
85
+
86
+ See [AGENTS.md](AGENTS.md) for how rules are laid out and released.
87
+
88
+ ## License
89
+
90
+ [MIT](LICENSE)
@@ -0,0 +1,11 @@
1
+ import { Plugin } from "@oxlint/plugins";
2
+ import { OxlintConfig } from "oxlint";
3
+ //#region src/index.d.ts
4
+ interface DeslopifyPlugin extends Plugin {
5
+ configs: {
6
+ recommended: OxlintConfig;
7
+ };
8
+ }
9
+ declare const plugin: DeslopifyPlugin;
10
+ //#endregion
11
+ export { plugin as default };
package/dist/index.mjs ADDED
@@ -0,0 +1,141 @@
1
+ import { eslintCompatPlugin } from "@oxlint/plugins";
2
+ //#region src/rules/docs-url.ts
3
+ function docsUrl(ruleName) {
4
+ return `https://github.com/BrianEstrada/oxlint-deslopify/blob/main/docs/rules/${ruleName}.md`;
5
+ }
6
+ //#endregion
7
+ //#region src/index.ts
8
+ const rules = {
9
+ "multiline-object": {
10
+ meta: {
11
+ type: "layout",
12
+ docs: {
13
+ description: "Put each property of an object literal with two or more properties on its own line.",
14
+ recommended: true,
15
+ url: docsUrl("multiline-object")
16
+ },
17
+ fixable: "whitespace",
18
+ messages: { singleLine: "Put each property of this object on its own line." }
19
+ },
20
+ createOnce: function(context) {
21
+ return { ObjectExpression: function(node) {
22
+ if (node.properties.length < 2 || node.loc.start.line !== node.loc.end.line) return;
23
+ context.report({
24
+ node,
25
+ messageId: "singleLine",
26
+ fix: function(fixer) {
27
+ return fixer.insertTextAfterRange([node.range[0], node.range[0] + 1], "\n");
28
+ }
29
+ });
30
+ } };
31
+ }
32
+ },
33
+ "no-graphql-cast": {
34
+ meta: {
35
+ type: "problem",
36
+ docs: {
37
+ description: "Require `graphql<unknown>` so GraphQL responses are parsed, not cast.",
38
+ recommended: false,
39
+ url: docsUrl("no-graphql-cast")
40
+ },
41
+ messages: { cast: "`graphql<T>` casts the response without checking it. Call `graphql<unknown>` and parse the result with a schema (e.g. Zod)." }
42
+ },
43
+ createOnce: function(context) {
44
+ return { CallExpression: function(node) {
45
+ const { callee } = node;
46
+ if (callee.type !== "MemberExpression" || callee.computed || callee.property.name !== "graphql") return;
47
+ const params = node.typeArguments?.params ?? [];
48
+ if (params.length === 1 && params[0]?.type === "TSUnknownKeyword") return;
49
+ context.report({
50
+ node,
51
+ messageId: "cast"
52
+ });
53
+ } };
54
+ }
55
+ },
56
+ "no-property-type-lookup": {
57
+ meta: {
58
+ type: "suggestion",
59
+ docs: {
60
+ description: "Disallow looking up a property's type with `T[\"key\"]`; name the type instead.",
61
+ recommended: true,
62
+ url: docsUrl("no-property-type-lookup")
63
+ },
64
+ messages: { lookup: "Don't look up a property's type with `T[\"{{key}}\"]`. Name that type (an interface, or `z.infer` of its own schema) and use the name." }
65
+ },
66
+ createOnce: function(context) {
67
+ return { TSIndexedAccessType: function(node) {
68
+ const { indexType } = node;
69
+ if (indexType.type !== "TSLiteralType" || indexType.literal.type !== "Literal") return;
70
+ const key = indexType.literal.value;
71
+ if (typeof key !== "string") return;
72
+ context.report({
73
+ node,
74
+ messageId: "lookup",
75
+ data: { key }
76
+ });
77
+ } };
78
+ }
79
+ },
80
+ "simple-ternary": {
81
+ meta: {
82
+ type: "suggestion",
83
+ docs: {
84
+ description: "Keep ternaries on one line, testing one condition, without interpolated strings.",
85
+ recommended: true,
86
+ url: docsUrl("simple-ternary")
87
+ },
88
+ messages: {
89
+ multiline: "Ternary spans multiple lines. Use if/else or extract a function.",
90
+ compoundTest: "Ternary test combines conditions with `{{operator}}`. Name the condition or use if/else.",
91
+ templateBranch: "Ternary branch builds an interpolated string. Build it outside the ternary."
92
+ }
93
+ },
94
+ createOnce: function(context) {
95
+ return { ConditionalExpression: function(node) {
96
+ if (node.loc.start.line !== node.loc.end.line) context.report({
97
+ node,
98
+ messageId: "multiline"
99
+ });
100
+ if (node.test.type === "LogicalExpression") context.report({
101
+ node: node.test,
102
+ messageId: "compoundTest",
103
+ data: { operator: node.test.operator }
104
+ });
105
+ for (const branch of [node.consequent, node.alternate]) if (branch.type === "TemplateLiteral" && branch.expressions.length > 0) context.report({
106
+ node: branch,
107
+ messageId: "templateBranch"
108
+ });
109
+ } };
110
+ }
111
+ }
112
+ };
113
+ const recommended = {
114
+ jsPlugins: ["oxlint-plugin-deslopify"],
115
+ rules: {
116
+ ...Object.fromEntries(Object.entries(rules).filter(([, rule]) => {
117
+ return rule.meta?.docs?.recommended === true;
118
+ }).map(([name]) => {
119
+ return [`deslopify/${name}`, "error"];
120
+ })),
121
+ "func-style": ["error", "declaration"],
122
+ "object-shorthand": ["error", "never"],
123
+ curly: ["error", "all"],
124
+ "typescript/consistent-type-definitions": ["error", "interface"],
125
+ "arrow-body-style": ["error", "always"],
126
+ "typescript/no-deprecated": "error",
127
+ "typescript/no-unsafe-type-assertion": "error",
128
+ "typescript/no-unsafe-assignment": "error",
129
+ "typescript/no-unsafe-member-access": "error",
130
+ "typescript/no-unsafe-argument": "error"
131
+ }
132
+ };
133
+ const plugin = {
134
+ ...eslintCompatPlugin({
135
+ meta: { name: "oxlint-plugin-deslopify" },
136
+ rules
137
+ }),
138
+ configs: { recommended }
139
+ };
140
+ //#endregion
141
+ export { plugin as default };
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "oxlint-plugin-deslopify",
3
+ "version": "0.1.0",
4
+ "description": "Oxlint rules that keep AI agents from writing sloppy code",
5
+ "keywords": [
6
+ "ai",
7
+ "eslint",
8
+ "eslint-plugin",
9
+ "eslintplugin",
10
+ "lint",
11
+ "oxlint",
12
+ "oxlint-plugin"
13
+ ],
14
+ "homepage": "https://github.com/BrianEstrada/oxlint-deslopify#readme",
15
+ "bugs": "https://github.com/BrianEstrada/oxlint-deslopify/issues",
16
+ "license": "MIT",
17
+ "author": "Brian Estrada",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/BrianEstrada/oxlint-deslopify.git"
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "type": "module",
26
+ "types": "./dist/index.d.mts",
27
+ "exports": {
28
+ ".": "./dist/index.mjs",
29
+ "./package.json": "./package.json"
30
+ },
31
+ "scripts": {
32
+ "build": "tsdown",
33
+ "typecheck": "tsc --noEmit",
34
+ "fmt": "oxfmt",
35
+ "fmt:check": "oxfmt --check",
36
+ "lint": "oxlint",
37
+ "lint:fix": "oxlint --fix",
38
+ "test": "node --test 'src/**/*.test.ts'",
39
+ "check": "bun run typecheck && bun run lint && bun run fmt:check && bun run test && bun run build",
40
+ "prepublishOnly": "bun run build"
41
+ },
42
+ "dependencies": {
43
+ "@oxlint/plugins": "^1.86.0"
44
+ },
45
+ "devDependencies": {
46
+ "@commitlint/cli": "21.2.3",
47
+ "@commitlint/config-conventional": "21.2.3",
48
+ "@types/node": "26.6.4",
49
+ "lefthook": "2.1.16",
50
+ "oxfmt": "0.71.0",
51
+ "oxlint": "1.86.0",
52
+ "oxlint-tsgolint": "7.0.2003",
53
+ "tsdown": "0.23.0",
54
+ "typescript": "7.0.2"
55
+ },
56
+ "peerDependencies": {
57
+ "oxlint": ">=1.86.0"
58
+ },
59
+ "engines": {
60
+ "node": "^20.19.0 || >=22.12.0"
61
+ }
62
+ }