adamantite 0.40.0 → 0.42.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 +194 -52
- package/dist/index.js +26 -35
- package/dist/presets/analyze.d.ts +19 -1
- package/dist/presets/analyze.js +16 -1
- package/dist/presets/lint/plugin/index.d.ts +15 -0
- package/dist/presets/lint/plugin/index.js +16 -0
- package/dist/presets/lint/plugin/options.d.ts +12 -0
- package/dist/presets/lint/plugin/options.js +33 -0
- package/dist/presets/lint/plugin/rules/no-overzealous-destructuring.d.ts +5 -0
- package/dist/presets/lint/plugin/rules/no-overzealous-destructuring.js +92 -0
- package/dist/presets/lint/plugin/rules/no-react-state-hooks.d.ts +10 -0
- package/dist/presets/lint/plugin/rules/no-react-state-hooks.js +160 -0
- package/dist/presets/lint/react-doctor.d.ts +5 -0
- package/dist/presets/lint/react-doctor.js +20 -0
- package/dist/presets/lint/react-strict.d.ts +5 -0
- package/dist/presets/lint/react-strict.js +11 -0
- package/dist/presets/lint/shadcn.d.ts +5 -0
- package/dist/presets/lint/shadcn.js +22 -0
- package/dist/presets/lint/strict.d.ts +5 -0
- package/dist/presets/lint/strict.js +18 -0
- package/dist/presets/lint/vendor/antislop/plugin.d.mts +18 -2
- package/package.json +10 -6
- package/skills/adamantite/SKILL.md +62 -23
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { readPositiveIntegerOption } from "../options.js";
|
|
2
|
+
//#region presets/lint/plugin/rules/no-overzealous-destructuring.ts
|
|
3
|
+
/**
|
|
4
|
+
* `const { data: { user } } = query` nests two patterns, which stays allowed.
|
|
5
|
+
*/
|
|
6
|
+
const DEFAULT_MAX_DEPTH = 2;
|
|
7
|
+
/**
|
|
8
|
+
* Five properties cover typical component props. A rest element does not count.
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_MAX_PROPERTIES = 5;
|
|
11
|
+
function isPattern(node) {
|
|
12
|
+
return node.type === "ObjectPattern" || node.type === "ArrayPattern";
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The pattern that directly contains `pattern`, through a property, a default value, or a rest
|
|
16
|
+
* element. A root pattern, such as a declaration target or a parameter, has none.
|
|
17
|
+
*/
|
|
18
|
+
function getParentPattern(pattern) {
|
|
19
|
+
let node = pattern.parent;
|
|
20
|
+
while (node.type === "AssignmentPattern" || node.type === "RestElement") node = node.parent;
|
|
21
|
+
if (node.type === "Property" && node.parent.type === "ObjectPattern") return node.parent;
|
|
22
|
+
return isPattern(node) ? node : void 0;
|
|
23
|
+
}
|
|
24
|
+
function getDepth(pattern) {
|
|
25
|
+
let depth = 1;
|
|
26
|
+
let parent = getParentPattern(pattern);
|
|
27
|
+
while (parent !== void 0) {
|
|
28
|
+
depth += 1;
|
|
29
|
+
parent = getParentPattern(parent);
|
|
30
|
+
}
|
|
31
|
+
return depth;
|
|
32
|
+
}
|
|
33
|
+
const rule = {
|
|
34
|
+
create(context) {
|
|
35
|
+
const maxDepth = readPositiveIntegerOption(context.options, "maxDepth") ?? DEFAULT_MAX_DEPTH;
|
|
36
|
+
const maxProperties = readPositiveIntegerOption(context.options, "maxProperties") ?? DEFAULT_MAX_PROPERTIES;
|
|
37
|
+
function checkDepth(node) {
|
|
38
|
+
const depth = getDepth(node);
|
|
39
|
+
if (depth === maxDepth + 1) context.report({
|
|
40
|
+
data: {
|
|
41
|
+
depth: String(depth),
|
|
42
|
+
maxDepth: String(maxDepth)
|
|
43
|
+
},
|
|
44
|
+
messageId: "tooDeep",
|
|
45
|
+
node
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
ArrayPattern: checkDepth,
|
|
50
|
+
ObjectPattern(node) {
|
|
51
|
+
checkDepth(node);
|
|
52
|
+
const count = node.properties.filter((property) => property.type === "Property").length;
|
|
53
|
+
if (count > maxProperties) context.report({
|
|
54
|
+
data: {
|
|
55
|
+
count: String(count),
|
|
56
|
+
maxProperties: String(maxProperties)
|
|
57
|
+
},
|
|
58
|
+
messageId: "tooManyProperties",
|
|
59
|
+
node
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
meta: {
|
|
65
|
+
defaultOptions: [{
|
|
66
|
+
maxDepth: DEFAULT_MAX_DEPTH,
|
|
67
|
+
maxProperties: DEFAULT_MAX_PROPERTIES
|
|
68
|
+
}],
|
|
69
|
+
docs: { description: "Disallow destructuring patterns that nest too deep or take too many properties from one object." },
|
|
70
|
+
messages: {
|
|
71
|
+
tooDeep: "This destructuring pattern is nested {{depth}} levels deep. The limit is {{maxDepth}}. Keep the outer object and read its members, or split the pattern into separate declarations.",
|
|
72
|
+
tooManyProperties: "This destructuring pattern takes {{count}} properties from one object. The limit is {{maxProperties}}. Keep the object and read its members where you use them, or split the pattern."
|
|
73
|
+
},
|
|
74
|
+
schema: [{
|
|
75
|
+
additionalProperties: false,
|
|
76
|
+
properties: {
|
|
77
|
+
maxDepth: {
|
|
78
|
+
minimum: 1,
|
|
79
|
+
type: "integer"
|
|
80
|
+
},
|
|
81
|
+
maxProperties: {
|
|
82
|
+
minimum: 1,
|
|
83
|
+
type: "integer"
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
type: "object"
|
|
87
|
+
}],
|
|
88
|
+
type: "suggestion"
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
//#endregion
|
|
92
|
+
export { rule as default };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Rule } from "@oxlint/plugins";
|
|
2
|
+
//#region presets/lint/plugin/rules/no-react-state-hooks.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Convert a glob to a regular expression that matches a whole path with forward slashes. Supports
|
|
5
|
+
* `**`, `*`, `?`, `[a-z]` and `[!a-z]` character classes, and `{a,b}` alternation.
|
|
6
|
+
*/
|
|
7
|
+
declare function globToRegExp(glob: string): RegExp;
|
|
8
|
+
declare const rule: Rule;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { rule as default, globToRegExp };
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { readStringArrayOption } from "../options.js";
|
|
2
|
+
//#region presets/lint/plugin/rules/no-react-state-hooks.ts
|
|
3
|
+
const HOOK_MESSAGES = /* @__PURE__ */ new Map([
|
|
4
|
+
["useCallback", "memoization"],
|
|
5
|
+
["useEffect", "effect"],
|
|
6
|
+
["useLayoutEffect", "effect"],
|
|
7
|
+
["useMemo", "memoization"],
|
|
8
|
+
["useReducer", "localState"],
|
|
9
|
+
["useState", "localState"],
|
|
10
|
+
["useSyncExternalStore", "externalStore"]
|
|
11
|
+
]);
|
|
12
|
+
const DEFAULT_HOOKS = [...HOOK_MESSAGES.keys()];
|
|
13
|
+
/**
|
|
14
|
+
* Modules where hooks are permitted: hook modules such as `useCart.ts` or `use-cart.ts`, and
|
|
15
|
+
* everything under a `hooks` directory. Everything else is feature code, including files such as
|
|
16
|
+
* `userProfile.tsx`.
|
|
17
|
+
*/
|
|
18
|
+
const DEFAULT_ALLOW = [
|
|
19
|
+
"**/use[A-Z]*.{ts,tsx}",
|
|
20
|
+
"**/use-*.{ts,tsx}",
|
|
21
|
+
"**/hooks/**"
|
|
22
|
+
];
|
|
23
|
+
const REACT_MODULE = "react";
|
|
24
|
+
function escapeRegExp(text) {
|
|
25
|
+
return text.replaceAll(/[$()*+.?[\\\]^{|}]/g, String.raw`\$&`);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Convert a glob to a regular expression that matches a whole path with forward slashes. Supports
|
|
29
|
+
* `**`, `*`, `?`, `[a-z]` and `[!a-z]` character classes, and `{a,b}` alternation.
|
|
30
|
+
*/
|
|
31
|
+
function globToRegExp(glob) {
|
|
32
|
+
let source = "";
|
|
33
|
+
let index = 0;
|
|
34
|
+
while (index < glob.length) {
|
|
35
|
+
const rest = glob.slice(index);
|
|
36
|
+
if (rest.startsWith("**/")) {
|
|
37
|
+
source += "(?:.*/)?";
|
|
38
|
+
index += 3;
|
|
39
|
+
} else if (rest === "/**") {
|
|
40
|
+
source += "(?:/.*)?";
|
|
41
|
+
index += 3;
|
|
42
|
+
} else if (rest.startsWith("**")) {
|
|
43
|
+
source += ".*";
|
|
44
|
+
index += 2;
|
|
45
|
+
} else if (rest.startsWith("*")) {
|
|
46
|
+
source += "[^/]*";
|
|
47
|
+
index += 1;
|
|
48
|
+
} else if (rest.startsWith("?")) {
|
|
49
|
+
source += "[^/]";
|
|
50
|
+
index += 1;
|
|
51
|
+
} else if (rest.startsWith("[") && rest.indexOf("]") > 1) {
|
|
52
|
+
const end = rest.indexOf("]");
|
|
53
|
+
const negated = rest.startsWith("[!");
|
|
54
|
+
const characters = rest.slice(negated ? 2 : 1, end).replaceAll(/[\\^]/g, String.raw`\$&`);
|
|
55
|
+
source += `[${negated ? "^" : ""}${characters}]`;
|
|
56
|
+
index += end + 1;
|
|
57
|
+
} else if (rest.startsWith("{") && rest.includes("}")) {
|
|
58
|
+
const end = rest.indexOf("}");
|
|
59
|
+
const alternatives = rest.slice(1, end).split(",").map((alternative) => escapeRegExp(alternative));
|
|
60
|
+
source += `(?:${alternatives.join("|")})`;
|
|
61
|
+
index += end + 1;
|
|
62
|
+
} else {
|
|
63
|
+
source += escapeRegExp(glob.charAt(index));
|
|
64
|
+
index += 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return new RegExp(`^${source}$`);
|
|
68
|
+
}
|
|
69
|
+
function toPosixPath(path) {
|
|
70
|
+
return path.replaceAll("\\", "/");
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The path that `allow` globs match: relative to the working directory when the file is inside it,
|
|
74
|
+
* and absolute otherwise.
|
|
75
|
+
*/
|
|
76
|
+
function getMatchPath(context) {
|
|
77
|
+
const filename = toPosixPath(context.filename);
|
|
78
|
+
const cwd = toPosixPath(context.cwd).replace(/\/$/, "");
|
|
79
|
+
return filename.startsWith(`${cwd}/`) ? filename.slice(cwd.length + 1) : filename;
|
|
80
|
+
}
|
|
81
|
+
function getImportedName(specifier) {
|
|
82
|
+
return specifier.imported.type === "Literal" ? specifier.imported.value : specifier.imported.name;
|
|
83
|
+
}
|
|
84
|
+
function isStringLiteral(node) {
|
|
85
|
+
return node.type === "Literal" && typeof node.value === "string";
|
|
86
|
+
}
|
|
87
|
+
function getPropertyName(member) {
|
|
88
|
+
if (!member.computed) return member.property.type === "Identifier" ? member.property.name : void 0;
|
|
89
|
+
return isStringLiteral(member.property) ? member.property.value : void 0;
|
|
90
|
+
}
|
|
91
|
+
function isCallee(node) {
|
|
92
|
+
const { parent } = node;
|
|
93
|
+
return parent?.type === "CallExpression" && parent.callee === node;
|
|
94
|
+
}
|
|
95
|
+
const rule = {
|
|
96
|
+
create(context) {
|
|
97
|
+
const hooks = new Set(readStringArrayOption(context.options, "hooks") ?? DEFAULT_HOOKS);
|
|
98
|
+
const allow = (readStringArrayOption(context.options, "allow") ?? DEFAULT_ALLOW).map((glob) => globToRegExp(glob));
|
|
99
|
+
const matchPath = getMatchPath(context);
|
|
100
|
+
if (allow.some((pattern) => pattern.test(matchPath))) return {};
|
|
101
|
+
function report(node, hook) {
|
|
102
|
+
context.report({
|
|
103
|
+
data: { hook },
|
|
104
|
+
messageId: HOOK_MESSAGES.get(hook) ?? "banned",
|
|
105
|
+
node
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function getReferences(specifier) {
|
|
109
|
+
return context.sourceCode.getDeclaredVariables(specifier).flatMap((variable) => variable.references).map((reference) => reference.identifier);
|
|
110
|
+
}
|
|
111
|
+
return { ImportDeclaration(node) {
|
|
112
|
+
if (node.source.value !== REACT_MODULE || node.importKind === "type") return;
|
|
113
|
+
for (const specifier of node.specifiers) {
|
|
114
|
+
if (specifier.type === "ImportSpecifier") {
|
|
115
|
+
const hook = getImportedName(specifier);
|
|
116
|
+
if (specifier.importKind === "type" || !hooks.has(hook)) continue;
|
|
117
|
+
for (const identifier of getReferences(specifier)) if (isCallee(identifier)) report(identifier.parent, hook);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
for (const identifier of getReferences(specifier)) {
|
|
121
|
+
const member = identifier.parent;
|
|
122
|
+
if (member.type !== "MemberExpression" || member.object !== identifier) continue;
|
|
123
|
+
const hook = getPropertyName(member);
|
|
124
|
+
if (hook !== void 0 && hooks.has(hook) && isCallee(member)) report(member.parent, hook);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} };
|
|
128
|
+
},
|
|
129
|
+
meta: {
|
|
130
|
+
defaultOptions: [{
|
|
131
|
+
allow: [...DEFAULT_ALLOW],
|
|
132
|
+
hooks: [...DEFAULT_HOOKS]
|
|
133
|
+
}],
|
|
134
|
+
docs: { description: "Disallow React state, effect, and memoization hooks in feature code. Hooks stay allowed in hook modules." },
|
|
135
|
+
messages: {
|
|
136
|
+
banned: "`{{hook}}` is not allowed in feature code. Move it into a hook module.",
|
|
137
|
+
effect: "`{{hook}}` runs a side effect in feature code. Move the side effect to a loader, an action, or an event handler.",
|
|
138
|
+
externalStore: "`{{hook}}` subscribes to an external store in feature code. Subscribe through a query or a store abstraction instead.",
|
|
139
|
+
localState: "`{{hook}}` keeps local state in feature code. Derive the value from loader data or URL params instead.",
|
|
140
|
+
memoization: "`{{hook}}` memoizes by hand. Remove it: the React Compiler handles memoization."
|
|
141
|
+
},
|
|
142
|
+
schema: [{
|
|
143
|
+
additionalProperties: false,
|
|
144
|
+
properties: {
|
|
145
|
+
allow: {
|
|
146
|
+
items: { type: "string" },
|
|
147
|
+
type: "array"
|
|
148
|
+
},
|
|
149
|
+
hooks: {
|
|
150
|
+
items: { type: "string" },
|
|
151
|
+
type: "array"
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
type: "object"
|
|
155
|
+
}],
|
|
156
|
+
type: "suggestion"
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
//#endregion
|
|
160
|
+
export { rule as default, globToRegExp };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region presets/lint/react-doctor.ts
|
|
2
|
+
const config = {
|
|
3
|
+
jsPlugins: ["oxlint-plugin-react-doctor"],
|
|
4
|
+
rules: {
|
|
5
|
+
"react-doctor/no-create-store-in-render": "error",
|
|
6
|
+
"react-doctor/no-direct-state-mutation": "error",
|
|
7
|
+
"react-doctor/no-eager-new-in-use-state-initializer": "error",
|
|
8
|
+
"react-doctor/no-impure-state-updater": "error",
|
|
9
|
+
"react-doctor/no-mutating-reducer-state": "error",
|
|
10
|
+
"react-doctor/effect-observer-needs-disconnect": "error",
|
|
11
|
+
"react-doctor/effect-raf-loop-needs-cancel": "error",
|
|
12
|
+
"react-doctor/no-effect-event-handler": "error",
|
|
13
|
+
"react-doctor/no-event-trigger-state": "error",
|
|
14
|
+
"react-doctor/no-fetch-in-effect": "error",
|
|
15
|
+
"react-doctor/no-pass-live-state-to-parent": "error",
|
|
16
|
+
"react-doctor/no-set-state-after-await-in-effect": "error"
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
//#endregion
|
|
20
|
+
export { config as default };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region presets/lint/react-strict.ts
|
|
2
|
+
const PLUGIN_EXTENSION = import.meta.url.endsWith(".ts") ? "ts" : "js";
|
|
3
|
+
const config = {
|
|
4
|
+
jsPlugins: [{
|
|
5
|
+
name: "adamantite",
|
|
6
|
+
specifier: new URL(`plugin/index.${PLUGIN_EXTENSION}`, import.meta.url).href
|
|
7
|
+
}],
|
|
8
|
+
rules: { "adamantite/no-react-state-hooks": "error" }
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
export { config as default };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region presets/lint/shadcn.ts
|
|
2
|
+
const config = {
|
|
3
|
+
jsPlugins: ["@shadcn/lint"],
|
|
4
|
+
overrides: [{
|
|
5
|
+
files: ["**/components/ui/**"],
|
|
6
|
+
rules: {
|
|
7
|
+
"shadcn/no-arbitrary-values": "off",
|
|
8
|
+
"shadcn/no-restyle": "off",
|
|
9
|
+
"shadcn/require-static-classes": "off"
|
|
10
|
+
}
|
|
11
|
+
}],
|
|
12
|
+
rules: {
|
|
13
|
+
"shadcn/no-arbitrary-values": ["error", { allow: ["layout"] }],
|
|
14
|
+
"shadcn/no-inline-styles": "error",
|
|
15
|
+
"shadcn/no-raw-colors": "error",
|
|
16
|
+
"shadcn/no-restyle": ["error", { allow: ["layout"] }],
|
|
17
|
+
"shadcn/no-unknown-classes": "error",
|
|
18
|
+
"shadcn/require-static-classes": "error"
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
export { config as default };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region presets/lint/strict.ts
|
|
2
|
+
const PLUGIN_EXTENSION = import.meta.url.endsWith(".ts") ? "ts" : "js";
|
|
3
|
+
const config = {
|
|
4
|
+
jsPlugins: [{
|
|
5
|
+
name: "adamantite",
|
|
6
|
+
specifier: new URL(`plugin/index.${PLUGIN_EXTENSION}`, import.meta.url).href
|
|
7
|
+
}],
|
|
8
|
+
overrides: [{
|
|
9
|
+
files: ["**/*.{test,spec}.{ts,tsx,mts,cts}", "**/__tests__/**"],
|
|
10
|
+
rules: { "typescript/consistent-type-assertions": "error" }
|
|
11
|
+
}],
|
|
12
|
+
rules: {
|
|
13
|
+
"adamantite/no-overzealous-destructuring": "error",
|
|
14
|
+
"typescript/consistent-type-assertions": ["error", { assertionStyle: "never" }]
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
export { config as default };
|
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
// Generated by scripts/vendor-plugins.ts — do not edit by hand.
|
|
2
2
|
interface VendoredPluginRule {
|
|
3
|
-
readonly create: (context:
|
|
3
|
+
readonly create: (context: object) => Record<string, never>
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
declare const vendoredPlugin: {
|
|
7
7
|
readonly meta: { readonly name: string }
|
|
8
|
-
readonly rules: {
|
|
8
|
+
readonly rules: {
|
|
9
|
+
readonly "no-chained-type-assertions": VendoredPluginRule
|
|
10
|
+
readonly "no-conditional-empty-object-spread": VendoredPluginRule
|
|
11
|
+
readonly "no-known-value-widening": VendoredPluginRule
|
|
12
|
+
readonly "no-module-mocking": VendoredPluginRule
|
|
13
|
+
readonly "no-object-parameters": VendoredPluginRule
|
|
14
|
+
readonly "no-reflect-apply": VendoredPluginRule
|
|
15
|
+
readonly "no-reflect-get": VendoredPluginRule
|
|
16
|
+
readonly "no-runtime-typeof": VendoredPluginRule
|
|
17
|
+
readonly "no-shape-in-symbol-names": VendoredPluginRule
|
|
18
|
+
readonly "no-unknown-parameters": VendoredPluginRule
|
|
19
|
+
readonly "no-unknown-returns": VendoredPluginRule
|
|
20
|
+
readonly "no-unknown-type-aliases": VendoredPluginRule
|
|
21
|
+
readonly "no-unsafe-dictionary-type": VendoredPluginRule
|
|
22
|
+
readonly "no-widen-then-assert": VendoredPluginRule
|
|
23
|
+
readonly "require-safety-comment-for-type-assertion": VendoredPluginRule
|
|
24
|
+
}
|
|
9
25
|
}
|
|
10
26
|
|
|
11
27
|
export default vendoredPlugin
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adamantite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.0",
|
|
4
4
|
"description": "An strict and opinionated set of presets for modern TypeScript applications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"adamantite",
|
|
@@ -58,18 +58,19 @@
|
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@clack/prompts": "1.7.0",
|
|
61
|
-
"@effect/platform-node": "4.0.0
|
|
61
|
+
"@effect/platform-node": "4.0.0",
|
|
62
62
|
"defu": "6.1.7",
|
|
63
|
-
"effect": "4.0.0
|
|
64
|
-
"esrap": "2.3.6",
|
|
63
|
+
"effect": "4.0.0",
|
|
65
64
|
"jsonc-parser": "3.3.1",
|
|
66
65
|
"nypm": "0.6.9",
|
|
67
|
-
"oxc-parser": "
|
|
66
|
+
"oxc-parser": "0.150.0"
|
|
68
67
|
},
|
|
69
68
|
"devDependencies": {
|
|
70
69
|
"@changesets/changelog-github": "1.0.0",
|
|
71
70
|
"@changesets/cli": "3.0.1",
|
|
72
|
-
"@effect/vitest": "4.0.0
|
|
71
|
+
"@effect/vitest": "4.0.0",
|
|
72
|
+
"@oxlint/plugins": "1.83.0",
|
|
73
|
+
"@shadcn/lint": "0.1.5",
|
|
73
74
|
"@tanstack/intent": "0.3.7",
|
|
74
75
|
"@types/figlet": "1.7.0",
|
|
75
76
|
"@types/node": "22.20.1",
|
|
@@ -79,6 +80,7 @@
|
|
|
79
80
|
"knip": "6.32.2",
|
|
80
81
|
"oxfmt": "0.65.0",
|
|
81
82
|
"oxlint": "1.83.0",
|
|
83
|
+
"oxlint-plugin-react-doctor": "0.9.14",
|
|
82
84
|
"oxlint-tsgolint": "7.0.2001",
|
|
83
85
|
"sherif": "1.13.0",
|
|
84
86
|
"tsdown": "0.22.14",
|
|
@@ -107,7 +109,9 @@
|
|
|
107
109
|
"test:build": "node scripts/test-build.ts",
|
|
108
110
|
"test:coverage": "vitest run --coverage",
|
|
109
111
|
"test:dist": "pnpm run build && pnpm run test:build && pnpm run test:smoke",
|
|
112
|
+
"test:presets": "vitest run --project presets",
|
|
110
113
|
"test:smoke": "node scripts/test-smoke.ts",
|
|
114
|
+
"test:unit": "vitest run --project unit",
|
|
111
115
|
"test:watch": "vitest",
|
|
112
116
|
"version": "changeset version"
|
|
113
117
|
}
|
|
@@ -22,25 +22,57 @@ non-interactive setup. Setup flags require `--non-interactive`, and at least one
|
|
|
22
22
|
`--script` is required:
|
|
23
23
|
|
|
24
24
|
```shell
|
|
25
|
-
npx adamantite init --non-interactive --script check --script fix --
|
|
25
|
+
npx adamantite init --non-interactive --script check --script fix --typescript --agents
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
Repeat `--script`, `--preset`, and `--editor` for multiple values. Available values are:
|
|
29
29
|
|
|
30
|
-
- Scripts: `check`, `fix`, `
|
|
31
|
-
- Presets: `react`, `nextjs`, `vue`, `jest`, `vitest`, `node`,
|
|
32
|
-
`
|
|
30
|
+
- Scripts: `check`, `fix`, `analyze`. In a detected monorepo, `analyze` also installs Sherif.
|
|
31
|
+
- Presets: `react`, `react-strict`, `react-doctor`, `nextjs`, `vue`, `jest`, `vitest`, `node`,
|
|
32
|
+
`strict`, `antislop`, `shadcn`;
|
|
33
|
+
editors: `vscode`, `zed`
|
|
33
34
|
- Optional flags: `--typescript`, `--install-extensions`, `--github-actions`, `--agents`,
|
|
34
35
|
`--overwrite-scripts`
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
The `react-strict` preset needs no extra package. Its `adamantite/no-react-state-hooks` rule
|
|
38
|
+
reports React state, effect, and memoization hooks outside hook modules (`useCart.ts`,
|
|
39
|
+
`use-cart.ts`, and files under `hooks/`), and each message tells what to do instead. Do not move a hook call into
|
|
40
|
+
a hook module only to silence the rule: follow the message. Set the rule's `allow` option
|
|
41
|
+
when the project keeps hooks elsewhere.
|
|
42
|
+
|
|
43
|
+
The `strict` preset needs no extra package either. Its `adamantite/no-overzealous-destructuring`
|
|
44
|
+
rule reports destructuring nested more than 2 levels deep or object patterns with more than 5
|
|
45
|
+
properties; keep the object and read its members, or split the pattern. Do not raise
|
|
46
|
+
`maxDepth` or `maxProperties` only to silence a report. The preset also bans type assertions
|
|
47
|
+
outside tests.
|
|
48
|
+
|
|
49
|
+
The `react-doctor` preset requires the `react` preset and the `oxlint-plugin-react-doctor`
|
|
50
|
+
package, which `init` installs at a pinned version and doctor and `update` keep in sync. It
|
|
51
|
+
enables curated React Doctor rules for React state and effect misuse, such as
|
|
52
|
+
`no-effect-event-handler` and `no-fetch-in-effect`, and adds to `react-strict`. The package has
|
|
53
|
+
a modified MIT license: using it as machine learning training data or selling it as a
|
|
54
|
+
hosted product needs written permission from the copyright holder. Tell the user before you
|
|
55
|
+
select the preset.
|
|
56
|
+
|
|
57
|
+
The `shadcn` preset needs Tailwind v4 and the `@shadcn/lint` package, which `init` installs
|
|
58
|
+
at a pinned version and doctor and `update` keep in sync. The plugin finds components and
|
|
59
|
+
the theme through `components.json`. Without that file it looks in `components/ui` or
|
|
60
|
+
`src/components/ui` and discovers the stylesheet that imports Tailwind; when components
|
|
61
|
+
live elsewhere, set `settings.shadcn` (for example `{ ui: "@/ds" }`) in `oxlint.config.ts`.
|
|
62
|
+
The preset turns off the component-owned rules under `**/components/ui/**`; add the same
|
|
63
|
+
override for another component directory.
|
|
64
|
+
|
|
65
|
+
Adamantite requires TypeScript 7 or later as a dependency in the project's own
|
|
66
|
+
`package.json`. Without that entry, npm cannot add or update a managed plugin.
|
|
67
|
+
|
|
68
|
+
Only select options supported by the project. Presets and TypeScript require `check` or `fix`;
|
|
69
|
+
extension installation requires an editor; `--github-actions` requires a CI-compatible script
|
|
70
|
+
and a supported package manager (bun, deno, npm, pnpm, or yarn). Omitted boolean flags are
|
|
71
|
+
disabled.
|
|
40
72
|
|
|
41
73
|
Existing package scripts whose commands differ from Adamantite's are kept and reported,
|
|
42
74
|
not replaced; pass `--overwrite-scripts` to replace them. Custom flags can be forwarded
|
|
43
|
-
to the Adamantite command after `--`, e.g. `adamantite
|
|
75
|
+
to the Adamantite command after `--`, e.g. `adamantite analyze -- --directory packages/app`.
|
|
44
76
|
|
|
45
77
|
## Daily workflow
|
|
46
78
|
|
|
@@ -49,25 +81,32 @@ Use the scripts written by `init` when available. Otherwise invoke the CLI direc
|
|
|
49
81
|
```shell
|
|
50
82
|
adamantite check
|
|
51
83
|
adamantite fix
|
|
52
|
-
adamantite format
|
|
53
|
-
adamantite format --check
|
|
54
84
|
adamantite analyze
|
|
55
|
-
adamantite monorepo
|
|
56
85
|
```
|
|
57
86
|
|
|
58
|
-
- Use `check` for read-only lint and type-error validation.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
- Use `analyze` for unused dependencies, exports, and files.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
87
|
+
- Use `check` for read-only formatting, lint, and type-error validation. `check --only lint` or `check --only format`
|
|
88
|
+
runs one stage.
|
|
89
|
+
- Use `fix` for automatic Oxlint fixes followed by Oxfmt formatting. Add `--suggested`, `--dangerous`, or `--all` only
|
|
90
|
+
with explicit permission after reviewing their impact. `fix --only lint` or `fix --only format` runs one stage.
|
|
91
|
+
- Use `analyze` for unused dependencies, exports, and files. In a monorepo it first checks
|
|
92
|
+
workspace dependency consistency with Sherif. `analyze --only monorepo` or
|
|
93
|
+
`analyze --only unused` runs one stage. `analyze --fix` may remove files, so inspect
|
|
94
|
+
findings before using it. Sherif refuses to fix when `CI` is set, so in a monorepo
|
|
95
|
+
`analyze --fix` fails there; use `analyze --only unused --fix` for the Knip fixes.
|
|
96
|
+
- In a monorepo, `analyze --fix` needs a terminal when Sherif must choose between versions.
|
|
97
|
+
Without a terminal, set `"sherif": { "select": "highest" }` in the root `package.json`, or
|
|
98
|
+
use `analyze --only unused --fix` to skip Sherif.
|
|
99
|
+
- `analyze` runs Sherif without flags. Put Sherif exceptions in the `sherif` field of the root
|
|
100
|
+
`package.json` (camelCase CLI options, e.g. `"ignoreDependency": ["tailwindcss"]`).
|
|
101
|
+
|
|
102
|
+
To pass arguments to Knip, Oxlint, or Sherif, place them after `--`. For `check` and `fix` they
|
|
103
|
+
go to Oxlint. For `analyze` they go to Knip, or to the stage that `--only` selects, so a Sherif
|
|
104
|
+
flag needs `--only monorepo`. For a permanent Sherif setting, use the `sherif` field instead.
|
|
67
105
|
|
|
68
106
|
```shell
|
|
69
107
|
adamantite check src -- --deny-warnings
|
|
70
108
|
bun run analyze -- -- --directory packages/app
|
|
109
|
+
adamantite analyze --only monorepo -- --ignore-package package-a
|
|
71
110
|
```
|
|
72
111
|
|
|
73
112
|
## Diagnose and repair
|
|
@@ -102,6 +141,6 @@ findings. Follow those findings and review the resulting diff.
|
|
|
102
141
|
- New target project: `init`, then run the configured checks.
|
|
103
142
|
- Suspected drift or broken setup: run `doctor` and follow its findings.
|
|
104
143
|
- Existing project upgrading Adamantite: `update`, then the doctor sequence.
|
|
105
|
-
- Code-quality failure: choose `check
|
|
106
|
-
|
|
144
|
+
- Code-quality failure: choose `check` or `analyze` based on the failing subsystem; do not
|
|
145
|
+
reinitialize the project.
|
|
107
146
|
- Unknown option or behavior: run `adamantite <command> --help` before guessing.
|