mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Clarify lint guardrail configuration
This commit is contained in:
parent
e6daf9db11
commit
19be43215e
3 changed files with 84 additions and 101 deletions
|
|
@ -43,26 +43,6 @@ const unsafeIteratorRestrictions = [
|
|||
message: "Avoid reading iterator.next().value directly; use for...of or inspect the typed IteratorResult first.",
|
||||
},
|
||||
];
|
||||
const chatStateRestrictions = [
|
||||
{
|
||||
selector: "AssignmentExpression[left.type='MemberExpression'][left.object.name='state']",
|
||||
message: "Route ChatState updates through ChatStateStore.dispatch().",
|
||||
},
|
||||
{
|
||||
selector: "AssignmentExpression[left.type='MemberExpression'][left.object.type='MemberExpression'][left.object.property.name='state']",
|
||||
message: "Route ChatState updates through ChatStateStore.dispatch().",
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.property.name=/^(push|set|delete|clear|add)$/][callee.object.type='MemberExpression'][callee.object.object.name='state']",
|
||||
message: "Clone ChatState collections and update them through ChatStateStore.dispatch().",
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.property.name=/^(push|set|delete|clear|add)$/][callee.object.type='MemberExpression'][callee.object.object.type='MemberExpression'][callee.object.object.property.name='state']",
|
||||
message: "Clone ChatState collections and update them through ChatStateStore.dispatch().",
|
||||
},
|
||||
];
|
||||
const pureChatModelRestrictions = [
|
||||
{
|
||||
selector: "CallExpression[callee.object.name='Date'][callee.property.name='now']",
|
||||
|
|
@ -90,11 +70,9 @@ const pureChatModelRestrictions = [
|
|||
},
|
||||
];
|
||||
const chatImperativeDomBridgeFiles = [
|
||||
"src/features/chat/ui/message-stream/**/*.{ts,tsx}",
|
||||
"src/features/chat/ui/composer.tsx",
|
||||
"src/features/chat/ui/goal-banner.tsx",
|
||||
"src/features/chat/ui/message-stream/markdown-renderer.ts",
|
||||
"src/features/chat/ui/message-stream/rendered-markdown-links.ts",
|
||||
"src/features/chat/ui/message-stream/**/*.tsx",
|
||||
"src/features/chat/ui/scroll.ts",
|
||||
"src/features/chat/ui/shell.tsx",
|
||||
"src/features/chat/ui/tool-result.tsx",
|
||||
|
|
@ -136,9 +114,64 @@ const codexPanelEslintPlugin = {
|
|||
};
|
||||
},
|
||||
},
|
||||
"no-chat-state-direct-mutation": {
|
||||
meta: {
|
||||
type: "problem",
|
||||
docs: {
|
||||
description: "Disallow direct ChatState mutation in chat modules.",
|
||||
},
|
||||
messages: {
|
||||
assign: "Route ChatState updates through ChatStateStore.dispatch().",
|
||||
mutateCollection: "Clone ChatState collections and update them through ChatStateStore.dispatch().",
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
create(context) {
|
||||
const mutatingCollectionMethods = new Set(["add", "clear", "delete", "push", "set"]);
|
||||
return {
|
||||
AssignmentExpression(node) {
|
||||
if (isChatStateMember(node.left)) context.report({ node: node.left, messageId: "assign" });
|
||||
},
|
||||
CallExpression(node) {
|
||||
if (!isMemberExpression(node.callee)) return;
|
||||
const method = staticPropertyName(node.callee.property);
|
||||
if (!method || !mutatingCollectionMethods.has(method)) return;
|
||||
if (isChatStateMember(node.callee.object)) context.report({ node: node.callee, messageId: "mutateCollection" });
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function isChatStateMember(node) {
|
||||
if (!isMemberExpression(node)) return false;
|
||||
|
||||
let current = node;
|
||||
while (isMemberExpression(current)) {
|
||||
if (isIdentifier(current.object, "state")) return true;
|
||||
if (isThisStateMember(current.object)) return true;
|
||||
current = current.object;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isThisStateMember(node) {
|
||||
return isMemberExpression(node) && node.object?.type === "ThisExpression" && staticPropertyName(node.property) === "state";
|
||||
}
|
||||
|
||||
function isMemberExpression(node) {
|
||||
return node?.type === "MemberExpression";
|
||||
}
|
||||
|
||||
function isIdentifier(node, name) {
|
||||
return node?.type === "Identifier" && node.name === name;
|
||||
}
|
||||
|
||||
function staticPropertyName(node) {
|
||||
return node?.type === "Identifier" ? node.name : node?.type === "Literal" && typeof node.value === "string" ? node.value : null;
|
||||
}
|
||||
|
||||
function findInitializerCallbackReference(root, name) {
|
||||
let reference = null;
|
||||
|
||||
|
|
@ -300,8 +333,8 @@ export default defineConfig([
|
|||
...unsafeIteratorRestrictions,
|
||||
...imperativeDomRestrictions,
|
||||
...preactFormRestrictions,
|
||||
...chatStateRestrictions,
|
||||
],
|
||||
"codex-panel/no-chat-state-direct-mutation": "error",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -312,8 +345,8 @@ export default defineConfig([
|
|||
...removedChatStateEscapeHatchRestrictions,
|
||||
...unsafeIteratorRestrictions,
|
||||
...preactFormRestrictions,
|
||||
...chatStateRestrictions,
|
||||
],
|
||||
"codex-panel/no-chat-state-direct-mutation": "error",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -336,9 +369,9 @@ export default defineConfig([
|
|||
...unsafeIteratorRestrictions,
|
||||
...imperativeDomRestrictions,
|
||||
...preactFormRestrictions,
|
||||
...chatStateRestrictions,
|
||||
...pureChatModelRestrictions,
|
||||
],
|
||||
"codex-panel/no-chat-state-direct-mutation": "error",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,16 @@ import { spawnSync } from "node:child_process";
|
|||
|
||||
const args = new Set(process.argv.slice(2));
|
||||
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
|
||||
const checks = [
|
||||
{ local: "typecheck", ci: "typecheck:ci", localPhase: "parallel" },
|
||||
{ local: "test", ci: "test:ci", localPhase: "parallel" },
|
||||
{ local: "lint:ts", ci: "lint:ts:ci", localPhase: "parallel" },
|
||||
{ local: "lint:css", ci: "lint:css", localPhase: "parallel" },
|
||||
{ local: "format:check", ci: "format:check:ci", localPhase: "parallel" },
|
||||
{ local: "build:styles:check", ci: "build:styles:check", localPhase: "parallel" },
|
||||
{ local: "unused", ci: "unused", localPhase: "parallel" },
|
||||
{ local: "build", ci: "build", localPhase: "sequential" },
|
||||
];
|
||||
|
||||
for (const arg of args) {
|
||||
if (arg !== "--ci") {
|
||||
|
|
@ -11,12 +21,11 @@ for (const arg of args) {
|
|||
}
|
||||
|
||||
if (args.has("--ci")) {
|
||||
for (const script of ["typecheck:ci", "test:ci", "lint:ts:ci", "lint:css", "format:check:ci", "build:styles:check", "unused", "build"]) {
|
||||
run(npmCommand, ["run", script]);
|
||||
}
|
||||
for (const check of checks) run(npmCommand, ["run", check.ci]);
|
||||
} else {
|
||||
run("node", ["scripts/run-parallel.mjs", "typecheck", "test", "lint:ts", "lint:css", "format:check", "build:styles:check", "unused"]);
|
||||
run(npmCommand, ["run", "build"]);
|
||||
const parallelScripts = checks.filter((check) => check.localPhase === "parallel").map((check) => check.local);
|
||||
run("node", ["scripts/run-parallel.mjs", ...parallelScripts]);
|
||||
for (const check of checks.filter((check) => check.localPhase === "sequential")) run(npmCommand, ["run", check.local]);
|
||||
}
|
||||
|
||||
function run(command, args) {
|
||||
|
|
|
|||
|
|
@ -4,33 +4,13 @@ export default {
|
|||
extends: ["stylelint-config-standard"],
|
||||
plugins: [noSpecificityWhere],
|
||||
rules: {
|
||||
"codex-panel/no-specificity-where": [
|
||||
true,
|
||||
{
|
||||
severity: "warning",
|
||||
},
|
||||
],
|
||||
"color-named": [
|
||||
"never",
|
||||
{
|
||||
severity: "warning",
|
||||
},
|
||||
],
|
||||
"color-no-hex": [
|
||||
true,
|
||||
{
|
||||
severity: "warning",
|
||||
},
|
||||
],
|
||||
"codex-panel/no-specificity-where": true,
|
||||
"color-named": "never",
|
||||
"color-no-hex": true,
|
||||
"custom-property-empty-line-before": null,
|
||||
"declaration-block-no-redundant-longhand-properties": null,
|
||||
"declaration-empty-line-before": null,
|
||||
"declaration-no-important": [
|
||||
true,
|
||||
{
|
||||
severity: "warning",
|
||||
},
|
||||
],
|
||||
"declaration-no-important": true,
|
||||
"declaration-property-value-disallowed-list": [
|
||||
{
|
||||
"/^(?:background(?:-color)?|border(?:-(?:block|inline|top|right|bottom|left))?(?:-color)?|box-shadow|caret-color|color|fill|outline(?:-color)?|stroke|text-shadow)$/":
|
||||
|
|
@ -45,7 +25,6 @@ export default {
|
|||
},
|
||||
{
|
||||
message: "Use Obsidian or Codex Panel design tokens instead of hardcoded colors or typography.",
|
||||
severity: "warning",
|
||||
},
|
||||
],
|
||||
"declaration-property-unit-disallowed-list": [
|
||||
|
|
@ -55,67 +34,29 @@ export default {
|
|||
},
|
||||
{
|
||||
message: "Prefer Obsidian or Codex Panel spacing/size tokens for layout dimensions.",
|
||||
severity: "warning",
|
||||
},
|
||||
],
|
||||
"function-disallowed-list": [
|
||||
["rgb", "rgba", "hsl", "hsla", "hwb", "lab", "lch", "oklab", "oklch"],
|
||||
{
|
||||
severity: "warning",
|
||||
},
|
||||
],
|
||||
"keyframes-name-pattern": [
|
||||
"^codex-panel-[a-z0-9-]+$",
|
||||
{
|
||||
severity: "warning",
|
||||
},
|
||||
],
|
||||
"max-nesting-depth": [
|
||||
2,
|
||||
{
|
||||
severity: "warning",
|
||||
},
|
||||
],
|
||||
"function-disallowed-list": ["rgb", "rgba", "hsl", "hsla", "hwb", "lab", "lch", "oklab", "oklch"],
|
||||
"keyframes-name-pattern": "^codex-panel-[a-z0-9-]+$",
|
||||
"max-nesting-depth": 2,
|
||||
"no-descending-specificity": null,
|
||||
"selector-class-pattern": null,
|
||||
"selector-max-compound-selectors": [
|
||||
4,
|
||||
{
|
||||
severity: "warning",
|
||||
},
|
||||
],
|
||||
"selector-max-specificity": [
|
||||
"0,4,0",
|
||||
{
|
||||
severity: "warning",
|
||||
},
|
||||
],
|
||||
"selector-max-compound-selectors": 4,
|
||||
"selector-max-specificity": "0,4,0",
|
||||
"selector-max-type": [
|
||||
0,
|
||||
{
|
||||
ignoreTypes: ["dd", "dt", "pre", "span", "summary", "svg", "ul"],
|
||||
severity: "warning",
|
||||
},
|
||||
],
|
||||
"selector-max-id": [
|
||||
0,
|
||||
{
|
||||
severity: "warning",
|
||||
},
|
||||
],
|
||||
"selector-max-id": 0,
|
||||
"selector-disallowed-list": [
|
||||
["/:has\\(/"],
|
||||
{
|
||||
message: "Avoid :has() because it can cause broad selector invalidation.",
|
||||
severity: "warning",
|
||||
},
|
||||
],
|
||||
"selector-max-universal": [
|
||||
0,
|
||||
{
|
||||
severity: "warning",
|
||||
},
|
||||
],
|
||||
"selector-max-universal": 0,
|
||||
"selector-not-notation": null,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue