diff --git a/data/demo-eslint/package.json b/data/demo-eslint/package.json new file mode 100644 index 0000000..4b2470d --- /dev/null +++ b/data/demo-eslint/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "@eslint/js": "^10.0.1", + "typescript-eslint": "^8.66.0" + } +} diff --git a/data/demo-eslint/reports/common-review.md b/data/demo-eslint/reports/common-review.md new file mode 100644 index 0000000..80e71a1 --- /dev/null +++ b/data/demo-eslint/reports/common-review.md @@ -0,0 +1,662 @@ +# 代码审查报告 + +**文件:** `data\demo-eslint\src\common.js` +**语言:** javascript +**耗时:** 288.0s +**分析工具:** eslint + +--- + +总计: 214 | 错误: 197 | 警告: 17 | 建议: 0 + +静态分析 · 210 个问题 + +- 🔴 `eslint:require-yield` L12 + L12: 此生成器函数没有 'yield'。 + 建议: 在生成器函数体内添加 yield 表达式;若本意不是生成器,请改为普通函数。 +- 🔴 `eslint:no-unused-vars` L12 + L12: 'generatorWithoutYield' 已定义但从未使用。 + 建议: 删除该函数声明;若它应被外部使用,请添加导出或实际调用。 +- 🔴 `eslint:no-undef` L13 + L13: 未定义变量 'console'。 + 建议: 在 ESLint 配置中声明浏览器或 Node 环境,或在文件顶部添加 /* global console */。 +- 🔴 `eslint:no-unused-vars` L17 + L17: 'asyncExecutor' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-async-promise-executor` L17 + L17: Promise 执行器不应是 async 函数。 + 建议: 移除 async 关键字,改为普通 executor,并使用 resolve/reject 控制 Promise 状态。 +- 🔴 `eslint:no-unused-vars` L17 + L17: 'reject' 已定义但从未使用。 + 建议: 若不需要拒绝路径,可省略 reject 参数,或实际调用 reject。 +- 🔴 `eslint:no-unused-vars` L22 + L22: 'executorReturn' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-unused-vars` L22 + L22: 'reject' 已定义但从未使用。 + 建议: 若不需要拒绝路径,可省略 reject 参数,或实际调用 reject。 +- 🔴 `eslint:no-promise-executor-return` L23 + L23: Promise 执行器的返回值无法被读取。 + 建议: 不要在 executor 内 return,直接调用 resolve 即可。 +- 🔴 `eslint:no-unused-vars` L27 + L27: 'awaitInLoop' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-undef` L30 + L30: 未定义变量 'fetch'。 + 建议: 确保运行环境为浏览器或 Node 18+,并在 ESLint 配置中声明对应全局变量。 +- 🔴 `eslint:no-unused-vars` L37 + L37: 'returnAssign' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-return-assign` L39 + L39: 返回语句中不应包含赋值。 + 建议: 将赋值与返回分开,避免歧义。 +- 🔴 `eslint:no-unused-vars` L39 + L39: 'assignedInReturn' 被赋值但从未使用。 + 建议: 若不需要该局部变量,可以直接 return x。 +- 🔴 `eslint:no-unused-vars` L43 + L43: 'throwLiteral' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-throw-literal` L44 + L44: 应抛出错误对象。 + 建议: 使用 new Error() 抛错,以保留堆栈信息。 +- 🔴 `eslint:no-unused-vars` L48 + L48: 'uselessReturn' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-undef` L54 + L54: 未定义变量 'console'。 + 建议: 在 ESLint 配置中声明浏览器或 Node 环境,或在文件顶部添加 /* global console */。 +- 🔴 `eslint:no-unused-vars` L62 + L62: 'BadConstructor' 已定义但从未使用。 + 建议: 删除该类,或在使用处实例化。 +- 🔴 `eslint:constructor-super` L63 + L63: 继承类构造函数中应调用 super()。 + 建议: 在构造函数体内最先调用 super(message)。 +- 🔴 `eslint:no-undef` L64 + L64: 未定义变量 'console'。 + 建议: 在 ESLint 配置中声明浏览器或 Node 环境,或在文件顶部添加 /* global console */。 +- 🔴 `eslint:no-class-assign` L70 + L70: 'Widget' 是类,不能重新赋值。 + 建议: 删除对类名的赋值,或用其他变量名保存覆盖后的值。 +- 🔴 `eslint:no-unused-vars` L70 + L70: 'Widget' 被赋值但从未使用。 + 建议: 删除该类或在使用处实例化。 +- 🔴 `eslint:no-unused-vars` L73 + L73: 'DupeMember' 已定义但从未使用。 + 建议: 删除该类,或在使用处实例化。 +- 🔴 `eslint:no-dupe-class-members` L77 + L77: 重复的成员名 'method'。 + 建议: 删除重复的方法定义,或改为不同方法名。 +- 🔴 `eslint:no-unused-vars` L83 + L83: 'EmptyStatic' 已定义但从未使用。 + 建议: 删除该类,或在使用处实例化。 +- 🔴 `eslint:no-empty-static-block` L84 + L84: 不应有空的 static 块。 + 建议: 删除空的 static 块,或在其中编写有意义的初始化逻辑。 +- 🔴 `eslint:no-unused-vars` L88 + L88: 'SetterReturn' 已定义但从未使用。 + 建议: 删除该类,或在使用处实例化。 +- 🔴 `eslint:no-setter-return` L91 + L91: setter 不能返回值。 + 建议: 删除 setter 中的 return 语句。 +- 🔴 `eslint:no-unused-vars` L96 + L96: 'GetterNoReturn' 已定义但从未使用。 + 建议: 删除该类,或在使用处实例化。 +- 🔴 `eslint:getter-return` L97 + L97: getter 'value' 应返回值。 + 建议: 在 getter 中返回一个有意义的表达式。 +- 🔴 `eslint:no-unused-vars` L98 + L98: 'x' 被赋值但从未使用。 + 建议: 删除 x 声明,或在实际使用后返回它。 +- 🔴 `eslint:no-unused-vars` L103 + L103: 'ThisBeforeSuper' 已定义但从未使用。 + 建议: 删除该类,或在使用处实例化。 +- 🔴 `eslint:no-this-before-super` L105 + L105: 在调用 super() 之前不允许使用 this。 + 建议: 将 super(message) 移到 this.message 赋值之前。 +- 🔴 `eslint:no-unused-vars` L111 + L111: 'UnusedPrivate' 已定义但从未使用。 + 建议: 删除该类,或在使用处实例化。 +- 🔴 `eslint:no-unused-private-class-members` L112 + L112: 私有成员 '#secret' 已定义但从未使用。 + 建议: 删除 #secret 定义,或在类方法中实际使用它。 +- 🔴 `eslint:no-unused-vars` L123 + L123: 'looseEquality' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:eqeqeq` L124 + L124: 应使用 '===' 而非 '=='。 + 建议: 将 == 改为 ===,避免隐式类型转换。 +- 🔴 `eslint:no-unused-vars` L128 + L128: 'nullEquality' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-eq-null` L129 + L129: 与 null 比较时应使用 '==='。 + 建议: 如果只想匹配 null,应使用 x === null;如需要 null/undefined 都匹配,可显式写成 x === null || x === undefined。 +- 🔴 `eslint:eqeqeq` L129 + L129: 应使用 '===' 而非 '=='。 + 建议: 将 == 改为 ===,避免隐式类型转换。 +- 🔴 `eslint:no-unused-vars` L133 + L133: 'selfCompare' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-self-compare` L134 + L134: 与自身比较可能没有意义。 + 建议: 删除该比较;若想检测 NaN 应使用 Number.isNaN(x)。 +- 🔴 `eslint:no-unused-vars` L138 + L138: 'negZero' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-compare-neg-zero` L139 + L139: 不要使用 '===' 与 -0 比较。 + 建议: 如果需要判断 -0,请使用 Object.is(x, -0)。 +- 🔴 `eslint:no-unused-vars` L143 + L143: 'isNanWrong' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:use-isnan` L144 + L144: 与 NaN 比较时应使用 isNaN 函数。 + 建议: 使用 Number.isNaN(x) 进行 NaN 判断。 +- 🔴 `eslint:no-unused-vars` L148 + L148: 'typeofCheck' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:valid-typeof` L149 + L149: typeof 比较值无效。 + 建议: 将 'strnig' 修正为 'string'。 +- 🔴 `eslint:no-unused-vars` L153 + L153: 'constBinary' 已定义但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-unused-vars` L171 + L171: 'fallthrough' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-undef` L174 + L174: 未定义变量 'console'。 + 建议: 在 ESLint 配置中声明浏览器或 Node 环境,或在文件顶部添加 /* global console */。 +- 🔴 `eslint:no-fallthrough` L175 + L175: 在 'case' 之前应有 'break' 语句。 + 建议: 在 case 1 的语句后添加 break,除非刻意需要穿透。 +- 🔴 `eslint:no-undef` L176 + L176: 未定义变量 'console'。 + 建议: 在 ESLint 配置中声明浏览器或 Node 环境,或在文件顶部添加 /* global console */。 +- 🔴 `eslint:no-unused-vars` L184 + L184: 'caseDeclarations' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-case-declarations` L187 + L187: case 块中不应有词法声明。 + 建议: 为 case 1 添加块级大括号 {},或把 let 声明移到 switch 外。 +- 🔴 `eslint:no-undef` L188 + L188: 未定义变量 'console'。 + 建议: 在 ESLint 配置中声明浏览器或 Node 环境,或在文件顶部添加 /* global console */。 +- 🔴 `eslint:no-unused-vars` L196 + L196: 'duplicateCase' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-duplicate-case` L200 + L200: 重复的 case 标签。 + 建议: 将第二个 case 1 改为其他值,或删除重复分支。 +- 🔴 `eslint:no-unused-vars` L208 + L208: 'dupeElseIf' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-dupe-else-if` L211 + L211: 此分支永远无法执行,其条件与前序 if-else-if 条件重复或已被覆盖。 + 建议: 删除重复的 else if 分支,或改为不同的条件。 +- 🔴 `eslint:no-unused-vars` L218 + L218: 'noDefault' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:default-case` L219 + L219: 应包含 default 分支。 + 建议: 在 switch 中添加 default 分支,处理未匹配的情况。 +- 🔴 `eslint:no-unused-vars` L227 + L227: 'defaultNotLast' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:default-case-last` L229 + L229: default 分支应放在最后。 + 建议: 将 default 分支移到 switch 的最后一个分支。 +- 🔴 `eslint:no-func-assign` L246 + L246: 'assignableFn' 是函数,不能重新赋值。 + 建议: 删除对函数名的重新赋值,或将新函数赋值给其他变量名。 +- 🔴 `eslint:no-unused-vars` L246 + L246: 'assignableFn' 被赋值但从未使用。 + 建议: 删除该函数定义或在使用处调用它。 +- 🔴 `eslint:no-unused-vars` L256 + L256: 'forDirection' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:for-direction` L257 + L257: 循环更新子句使变量向错误方向移动。 + 建议: 将 i-- 改为 i++,使循环条件 i < 10 最终为 false。 +- 🔴 `eslint:no-undef` L258 + L258: 未定义变量 'console'。 + 建议: 在 ESLint 配置中声明浏览器或 Node 环境,或在文件顶部添加 /* global console */。 +- 🔴 `eslint:no-unused-vars` L267 + L267: 'my_snake_case_var' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-unused-vars` L270 + L270: 'generated' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-undef` L270 + L270: 未定义变量 'object'。 + 建议: 检查全局是否存在 object 构造函数;更推荐使用 {} 创建空对象。 +- 🔴 `eslint:no-unused-vars` L273 + L273: 'uselessConcat' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-unused-vars` L276 + L276: 'obj' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-unused-vars` L279 + L279: 'dupeKeyObj' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-dupe-keys` L279 + L279: 属性名 'a' 重复。 + 建议: 删除重复的 a 属性,保留最终需要的值。 +- 🔴 `eslint:no-unused-vars` L282 + L282: 'a' 被赋值但从未使用。 + 建议: 删除该解构赋值,或在使用处引用 a。 +- 🔴 `eslint:no-unused-vars` L287 + L287: 'preferTemplate' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-unused-vars` L292 + L292: 'preferSpreadStyle' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-unused-vars` L298 + L298: 'preferRest' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-unused-vars` L307 + L307: 'preferSpreadCall' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-unused-vars` L313 + L313: 'hasOwnPrefer' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-shadow` L313 + L313: 'obj' 与第 276 行第 7 列的上层作用域中的 'obj' 重名。 + 建议: 将参数改名为 target 或 sourceObj,避免遮蔽外层变量。 +- 🔴 `eslint:no-unused-vars` L318 + L318: 'preferConstDemo' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-useless-assignment` L323 + L323: 此赋值在后续语句中未被使用。 + 建议: 删除 someObj 声明,或在实际使用后保留。 +- 🔴 `eslint:no-unused-vars` L334 + L334: 'extendNative' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-extend-native` L335 + L335: String 原型是只读的,不应添加属性。 + 建议: 不要修改原生原型;如需扩展功能,应使用包装函数或 WeakMap。 +- 🔴 `eslint:no-unused-vars` L339 + L339: 'evalDemo' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-eval` L340 + L340: eval 可能存在安全风险。 + 建议: 避免使用 eval;如果只是解析 JSON,请用 JSON.parse。 +- 🔴 `eslint:no-unused-vars` L344 + L344: 'paramReassign' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-unused-vars` L350 + L350: 'unmodifiedLoop' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-unmodified-loop-condition` L351 + L351: 循环中 'i' 未被修改。 + 建议: 在循环体内修改 i,否则该循环可能无限执行。 +- 🔴 `eslint:no-undef` L352 + L352: 未定义变量 'console'。 + 建议: 在 ESLint 配置中声明浏览器或 Node 环境,或在文件顶部添加 /* global console */。 +- 🔴 `eslint:no-unused-vars` L357 + L357: 'unreachableLoop' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-unreachable-loop` L359 + L359: 无效循环,其循环体只能执行一次。 + 建议: 删除循环或移除循环体中的 break。 +- 🔴 `eslint:no-unused-vars` L359 + L359: 'item' 被赋值但从未使用。 + 建议: 若不需要 item,改用索引循环或 for...of 中实际使用 item。 +- 🔴 `eslint:no-unreachable-loop` L362 + L362: 无效循环,其循环体只能执行一次。 + 建议: 将 while(true) 改为 if(true),或移除 break 并重写逻辑。 +- 🔴 `eslint:no-unused-vars` L376 + L376: 'badRegex' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-invalid-regexp` L376 + L376: 无效正则表达式:/[/: 未结束的字符类。 + 建议: 如果要匹配左括号,应使用 new RegExp('\\[')。 +- 🔴 `eslint:no-unused-vars` L379 + L379: 'regexSpaces' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-regex-spaces` L379 + L379: 空格难以计数,请使用 {2}。 + 建议: 将连续空格改为 {2} 量词。 +- 🔴 `eslint:no-unused-vars` L382 + L382: 'controlRegex' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-control-regex` L382 + L382: 正则表达式中出现意外的控制字符:\x1f。 + 建议: 如果确实要匹配控制字符,请添加注释并临时禁用该规则;否则应移除该正则。 +- 🔴 `eslint:no-unused-vars` L385 + L385: 'emptyCharClass' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-empty-character-class` L385 + L385: 空字符类。 + 建议: 删除空的 [],或写入实际需要的字符。 +- 🔴 `eslint:no-unused-vars` L388 + L388: 'misleadingCharClass' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-misleading-character-class` L388 + L388: 字符类中出现意外的代理对,请使用 'u' 标志。 + 建议: 为正则添加 u 标志。 +- 🔴 `eslint:no-misleading-character-class` L388 + L388: 字符类中出现意外的代理对,请使用 'u' 标志。 + 建议: 为正则添加 u 标志。 +- 🔴 `eslint:no-unused-vars` L391 + L391: 'uselessBackref' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-useless-backreference` L391 + L391: 反向引用 '\1' 将被忽略,它引用了自身所在的组 '(\1a)'。 + 建议: 删除无意义的反向引用,或重写分组。 +- 🔴 `eslint:no-unused-vars` L394 + L394: 'uselessEscape' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-useless-escape` L394 + L394: 多余的转义字符:\.。 + 建议: 删除多余的转义,写成 'line.'。 +- 🔴 `eslint:no-unused-vars` L397 + L397: 'irregularWhitespace' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-loss-of-precision` L400 + L400: 此数字字面量在运行时将丢失精度。 + 建议: 使用可安全表示的浮点字面量,或改用 BigInt/字符串处理高精度数值。 +- 🔴 `eslint:no-sparse-arrays` L403 + L403: 数组中不应出现空槽位。 + 建议: 用 undefined 显式填充空位,或删除该槽位。 +- 🔴 `eslint:no-unused-vars` L406 + L406: 'emptyFunctionBody' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-empty` L409 + L409: 空块语句。 + 建议: 在 if 块中填充逻辑,或删除空的 if 语句。 +- 🔴 `eslint:no-unused-vars` L413 + L413: 'constantBinary' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-constant-binary-expression` L413 + L413: '&&' 表达式的左侧出现意外的常量真值。 + 建议: 移除恒真左侧,或改写为普通值。 +- 🔴 `eslint:no-constant-condition` L416 + L416: 意外的常量条件。 + 建议: 删除固定的 if(true),或改为真实条件。 +- 🔴 `eslint:no-undef` L417 + L417: 未定义变量 'console'。 + 建议: 在 ESLint 配置中声明浏览器或 Node 环境,或在文件顶部添加 /* global console */。 +- 🔴 `eslint:no-cond-assign` L422 + L422: 应是条件表达式,而不是赋值。 + 建议: 将 = 改为 ===,或者把赋值移到 if 之前。 +- 🔴 `eslint:no-constant-condition` L422 + L422: 意外的常量条件。 + 建议: 修正条件赋值后,条件将不再是常量。 +- 🔴 `eslint:no-unused-vars` L422 + L422: 'condVar' 被赋值但从未使用。 + 建议: 删除该变量,或在实际条件中使用它。 +- 🔴 `eslint:no-undef` L423 + L423: 未定义变量 'console'。 + 建议: 在 ESLint 配置中声明浏览器或 Node 环境,或在文件顶部添加 /* global console */。 +- 🔴 `eslint:no-unused-vars` L428 + L428: 'selfAssigned' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-useless-assignment` L428 + L428: 此赋值在后续语句中未被使用。 + 建议: 删除自赋值语句。 +- 🔴 `eslint:no-self-assign` L428 + L428: 'selfAssigned' 被赋值为自身。 + 建议: 删除该无意义的自赋值。 +- 🔴 `eslint:no-debugger` L431 + L431: 不应使用 'debugger' 语句。 + 建议: 删除 debugger 语句。 +- 🔴 `eslint:no-extra-boolean-cast` L434 + L434: 多余的双重否定。 + 建议: 移除 !!,直接使用变量作为条件。 +- 🔴 `eslint:no-undef` L435 + L435: 未定义变量 'console'。 + 建议: 在 ESLint 配置中声明浏览器或 Node 环境,或在文件顶部添加 /* global console */。 +- 🔴 `eslint:no-ex-assign` L442 + L442: 不应给 catch 异常参数赋值。 + 建议: 不要重新赋值 e;如需重新抛出,请直接 throw e 或 throw new Error(..., { cause: e })。 +- 🔴 `eslint:no-unused-vars` L442 + L442: 'e' 被赋值但从未使用。 + 建议: 删除赋值;如果需要新错误,直接抛出而不是赋值给 e。 +- 🔴 `eslint:no-unused-vars` L446 + L446: 'noEmptyPattern' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-empty-pattern` L446 + L446: 不应使用空对象解构模式。 + 建议: 在解构模式中写出至少一个属性名,或直接使用普通参数。 +- 🔴 `eslint:no-unused-vars` L451 + L451: 'unreachableCode' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-unreachable` L453 + L453: 不可达代码。 + 建议: 删除 return 之后永远不会执行的语句。 +- 🔴 `eslint:no-undef` L453 + L453: 未定义变量 'console'。 + 建议: 在 ESLint 配置中声明浏览器或 Node 环境,或在文件顶部添加 /* global console */。 +- 🔴 `eslint:no-unused-vars` L457 + L457: 'unexpectedMultiline' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-unexpected-multiline` L458 + L458: 函数与函数调用左括号之间存在意外换行。 + 建议: 在前一行末尾补充分号,或将 IIFE 整体改成赋值表达式,避免被解释为对上文变量的调用。 +- 🔴 `eslint:no-undef` L459 + L459: 未定义变量 'console'。 + 建议: 在 ESLint 配置中声明浏览器或 Node 环境,或在文件顶部添加 /* global console */。 +- 🔴 `eslint:no-unused-vars` L463 + L463: 'unsafeOptional' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-shadow` L463 + L463: 'obj' 与第 276 行第 7 列的上层作用域中的 'obj' 重名。 + 建议: 将参数改名为 target 或 sourceObj,避免遮蔽外层变量。 +- 🔴 `eslint:no-unsafe-optional-chaining` L464 + L464: 可选链使用不安全;如果短路为 'undefined',后续运算会抛 TypeError。 + 建议: 将后续属性访问也改为可选链,或先判断 obj?.a 是否存在。 +- 🔴 `eslint:no-unsafe-negation` L470 + L470: 不应取反 'in' 运算符的左操作数。 + 建议: 将整个 in 表达式放入括号后再取反,例如 !(flagFlag in holderObj)。 +- 🔴 `eslint:no-undef` L471 + L471: 未定义变量 'console'。 + 建议: 在 ESLint 配置中声明浏览器或 Node 环境,或在文件顶部添加 /* global console */。 +- 🔴 `eslint:no-unused-vars` L475 + L475: 'unsafeFinally' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-unsafe-finally` L479 + L479: 在 finally 中使用返回语句不安全。 + 建议: 不要在 finally 中 return,否则会覆盖 try 中的 throw 或 return。 +- 🔴 `eslint:no-unused-labels` L484 + L484: 标签 'unusedLabel:' 已定义但从未使用。 + 建议: 移除未使用的标签。 +- 🔴 `eslint:no-undef` L485 + L485: 未定义变量 'console'。 + 建议: 在 ESLint 配置中声明浏览器或 Node 环境,或在文件顶部添加 /* global console */。 +- 🔴 `eslint:no-unused-vars` L489 + L489: 'completelyUnusedVariable' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-unused-vars` L492 + L492: 'uselessCatch' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-useless-catch` L493 + L493: 不必要的 try/catch 包装。 + 建议: 删除 try/catch,直接返回 try 中的值。 +- 🔴 `eslint:no-unreachable` L495 + L495: 不可达代码。 + 建议: 删除 catch 中被 no-useless-catch 覆盖后不再可达的 throw 逻辑。 +- 🔴 `eslint:no-unused-vars` L514 + L514: 'irregularWS' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-irregular-whitespace` L514 + L514: 不允许使用不规则空白。 + 建议: 将行尾的不可见空白(如 NBSP)替换为普通空格或删除。 +- 🔴 `eslint:no-var` L521 + L521: 不应使用 var,请改用 let 或 const。 + 建议: 改用 const,若需要重新赋值则改用 let。 +- 🔴 `eslint:no-unused-vars` L521 + L521: 'oldStyleVar' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-undef` L524 + L524: 未定义变量 'console'。 + 建议: 在 ESLint 配置中声明浏览器或 Node 环境,或在文件顶部添加 /* global console */。 +- 🔴 `eslint:no-undef` L524 + L524: 未定义变量 'undefinedGlobalThing'。 + 建议: 检查该全局变量是否真实存在;若存在,请添加全局声明。 +- 🔴 `eslint:no-unused-vars` L527 + L527: 'redeclareDemo' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-var` L528 + L528: 不应使用 var,请改用 let 或 const。 + 建议: 改用 let 或 const。 +- 🔴 `eslint:no-var` L529 + L529: 不应使用 var,请改用 let 或 const。 + 建议: 删除重复声明,并保留一次用 let/const 的声明。 +- 🔴 `eslint:no-redeclare` L529 + L529: 'redeclaredName' 已定义。 + 建议: 删除重复的 var 声明,只在函数中声明一次。 +- 🔴 `eslint:no-unused-vars` L529 + L529: 'redeclaredName' 被赋值但从未使用。 + 建议: 保留一次声明并在使用处引用,或删除整个演示函数。 +- 🔴 `eslint:no-unused-vars` L539 + L539: 'deleteVarDemo' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-shadow` L540 + L540: 'obj' 与第 276 行第 7 列的上层作用域中的 'obj' 重名。 + 建议: 将局部变量 obj 改名为 targetObj 或 instance。 +- 🔴 `eslint:no-unused-vars` L545 + L545: 'shadowRestricted' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-shadow-restricted-names` L545 + L545: 不允许遮蔽全局属性 'undefined'。 + 建议: 将参数 undefined 改名为 other 或 anyValue。 +- 🔴 `eslint:no-unused-vars` L550 + L550: 'globalAssign' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-global-assign` L551 + L551: 只读全局变量 'Math' 不应被修改。 + 建议: 不要给 Math 赋值;如果需要自定义数学方法,请使用新的命名空间。 +- 🔴 `eslint:no-unused-vars` L558 + L558: 'blockScopedDemo' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-constant-condition` L559 + L559: 意外的常量条件。 + 建议: 移除 if(true),或改为基于真实条件。 +- 🔴 `eslint:no-var` L560 + L560: 不应使用 var,请改用 let 或 const。 + 建议: 在函数顶部改用 let/const 声明,并确保块级语义正确。 +- 🔴 `eslint:no-undef` L562 + L562: 未定义变量 'console'。 + 建议: 在 ESLint 配置中声明浏览器或 Node 环境,或在文件顶部添加 /* global console */。 +- 🔴 `eslint:block-scoped-var` L562 + L562: 在第 560 行声明的 'blockScoped' 在绑定上下文之外使用。 + 建议: 将 var 移到函数顶部,或改用 let/const 并确保作用域一致。 +- 🔴 `eslint:no-unused-vars` L566 + L566: 'unassignedVarDemo' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-unassigned-vars` L567 + L567: 'unassignedVariable' 因从未赋值而始终为 'undefined'。 + 建议: 初始化变量,或检查是否真的需要该变量。 +- 🔴 `eslint:no-unused-vars` L572 + L572: 'shadowDemo' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-unused-vars` L573 + L573: 'value' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-shadow` L575 + L575: 'value' 与第 573 行第 7 列的上层作用域中的 'value' 重名。 + 建议: 将内层变量改名为 innerValue。 +- 🔴 `eslint:no-unused-vars` L582 + L582: 'uselessAssignment' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-useless-assignment` L583 + L583: 此赋值在后续语句中未被使用。 + 建议: 删除初始赋值,只保留最终赋值。 +- 🔴 `eslint:no-unused-vars` L589 + L589: 'arrayViaConstructor' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-array-constructor` L589 + L589: 应优先使用数组字面量 []。 + 建议: 改用 [1, 2, 3]。 +- 🔴 `eslint:no-unused-vars` L592 + L592: 'symbolViaNew' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-new-native-nonconstructor` L592 + L592: 'Symbol' 不能作为构造函数调用。 + 建议: 移除 new,直接调用 Symbol('x')。 +- 🔴 `eslint:no-unused-vars` L595 + L595: 'jsonAsFunction' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-obj-calls` L595 + L595: 'JSON' 不是函数。 + 建议: 不要直接调用 JSON();如需解析 JSON 字符串,应使用 JSON.parse()。 +- 🔴 `eslint:no-const-assign` L599 + L599: 'immutableValue' 是常量,不能重新赋值。 + 建议: 改用 let,或删除赋值。 +- 🔴 `eslint:no-unused-vars` L599 + L599: 'immutableValue' 被赋值但从未使用。 + 建议: 删除该变量,或在使用处引用它。 +- 🔴 `eslint:no-unused-vars` L602 + L602: 'protoBuilds' 已定义但从未使用。 + 建议: 删除该函数,或在使用处调用它。 +- 🔴 `eslint:no-shadow` L602 + L602: 'obj' 与第 276 行第 7 列的上层作用域中的 'obj' 重名。 + 建议: 将参数改名为 target 或 sourceObj,避免遮蔽外层变量。 +- 🔴 `eslint:no-prototype-builtins` L603 + L603: 不应从目标对象上访问 Object.prototype 的方法 'hasOwnProperty'。 + 建议: 使用 Object.hasOwn(obj, 'key') 或 Object.prototype.hasOwnProperty.call(obj, 'key')。 +- 🟡 `eslint:no-await-in-loop` L30 + L30: 循环内不应使用 await。 + 建议: 若请求之间无依赖,应使用 Promise.all 并行执行;若有依赖,至少在循环外积累 Promise。 +- 🟡 `eslint:no-useless-return` L50 + L50: 不必要的 return 语句。 + 建议: 删除函数末尾无意义的 return。 +- 🟡 `eslint:prefer-template` L154 + L154: 不应使用字符串拼接。 + 建议: 改用模板字符串或直接写出拼接结果;注意当前表达式结果可能是 'aNaN'。 +- 🟡 `eslint:camelcase` L267 + L267: 标识符 'my_snake_case_var' 不是驼峰命名。 + 建议: 改为驼峰命名,如 mySnakeCaseVar。 +- 🟡 `eslint:new-cap` L270 + L270: 构造函数名不应以小写字母开头。 + 建议: 如果意图是创建对象,应使用 new Object() 或对象字面量 {}。 +- 🟡 `eslint:no-useless-concat` L273 + L273: 不应拼接字符串字面量。 + 建议: 直接写 'foobar'。 +- 🟡 `eslint:no-useless-computed-key` L276 + L276: 属性 ['key'] 的计算属性是不必要的。 + 建议: 直接写成 key: 'value'。 +- 🟡 `eslint:no-useless-rename` L282 + L282: 解构赋值 a 被无必要地重命名。 + 建议: 简化为 { a }。注意 someObj 当前在 L282 之后才声明,会导致运行时错误。 +- 🟡 `eslint:prefer-template` L288 + L288: 不应使用字符串拼接。 + 建议: 改用模板字符串。 +- 🟡 `eslint:prefer-object-spread` L293 + L293: 应使用对象展开代替 Object.assign,例如 `{ ...foo }`。 + 建议: 改用对象字面量展开语法。 +- 🟡 `eslint:prefer-rest-params` L299 + L299: 应使用剩余参数代替 'arguments'。 + 建议: 将函数签名改为 function preferRest(...args),并直接返回 args。 +- 🟡 `eslint:prefer-spread` L309 + L309: 应使用展开运算符代替 '.apply()'。 + 建议: 使用 spreadThis(...nums)。 +- 🟡 `eslint:prefer-object-has-own` L314 + L314: 应使用 Object.hasOwn() 代替 Object.prototype.hasOwnProperty.call()。 + 建议: 改用 Object.hasOwn(obj, key)。 +- 🟡 `eslint:no-param-reassign` L345 + L345: 不应重新赋值函数参数 'x'。 + 建议: 改用局部变量保存计算结果。 +- 🟡 `eslint:new-cap` L595 + L595: 以大写字母开头的函数只应作为构造函数使用。 + 建议: JSON 不是构造函数,也不可调用;使用 JSON.parse 等静态方法。 + +AI 审查 · 4 条建议 + +- 🔴 [AI] [design] `top-level-fatal-expression` L270 + **顶层存在多个必然抛出异常的表达式,会导致脚本加载中断** + 合并文件在模块初始化阶段依次执行大量演示代码,包括 `new object()`(若 object 未定义会抛 ReferenceError)、`new RegExp('[')`(抛 SyntaxError)、`new Symbol('x')`(抛 TypeError)、`JSON()`(抛 TypeError)、`immutableValue = 2`(抛 TypeError)等。这些不是普通 lint 风格问题,而是脚本加载即崩溃的致命缺陷。即使静态分析逐条标记了相关规则,文件的“可运行性”仍无法保证。 + 建议: 将演示/规则触发代码放入独立函数或测试用例中,避免在模块顶层直接执行会抛异常的字面量表达式;必要时用 try/catch 包住这些演示片段。 +- 🔴 [AI] [bug] `use-before-define` L282 + **someObj 在声明前使用导致暂时性死区错误** + L282 的 `const { a: a } = someObj;` 引用了 L323 才声明的 `const someObj`。const 声明存在暂时性死区,代码执行到 L282 时会抛出 ReferenceError,导致合并脚本在后续逻辑执行前中断。 + 建议: 将 `const someObj = ...` 移到 L282 之前,或在 L282 直接使用对象字面量,避免引用未初始化的变量。 +- 🟡 [AI] [bug] `string-coercion-bug` L154 + **字符串拼接 `-'b'` 产生 'aNaN',很可能不是预期结果** + L154 的 `const y = 'a' + -'b';` 中 `-'b'` 的结果是 NaN,字符串拼接后得到 'aNaN'。若意图是拼接两个字符串,应直接写 'ab';若意图是数学运算,则操作数不应为字符串。 + 建议: 根据意图改写为 `const y = 'ab';` 或 `const y = 'a' + 'b';`。 +- 🟡 [AI] [bug] `exception-swallowed` L442 + **catch 参数被重新赋值,原始异常被静默吞掉** + L442 的 `e = new Error('reassigned');` 只是改变 catch 局部变量 e 的引用,原始异常没有被记录也没有被重新抛出,这段 catch 块的语义很可能是错误的:异常在无人知晓的情况下被吞掉。 + 建议: 如需记录并重新抛出,应使用 `console.error(e); throw new Error('reassigned', { cause: e });`;如果只是演示重抛,应该写 `throw e;`。 diff --git a/data/demo-eslint/reports/demo-eslint插件实测覆盖率报告.md b/data/demo-eslint/reports/demo-eslint插件实测覆盖率报告.md new file mode 100644 index 0000000..21bf0d8 --- /dev/null +++ b/data/demo-eslint/reports/demo-eslint插件实测覆盖率报告.md @@ -0,0 +1,119 @@ +# demo-eslint 插件实测覆盖率报告 + +> **工程**:`demo-eslint`(纯内置配置形态:无 `eslint.config.*`) +> **测试对象**:插件 ESLint 适配器(引擎 eslint 9.39.4 + @eslint/js 9.39.4 + typescript-eslint 8.60.1) +> **实测日期**:2026-08-25 +> **比对基线**:插件同版本依赖复现的模拟基线(比对键 = 规则 + 行号 + 严重级别) + +--- + +## 一、核心结论 + +| 指标 | 结果 | 状态 | +|---|---|---| +| 插件行为一致性(vs 模拟基线) | **292/292 条逐条一致,零偏差** | ✅ 100% | +| 规则覆盖率(真实规则口径) | **126/126 种(100%)** | ✅ 全覆盖 | +| 解析错误 | **0 处** | ✅ | +| 诊断总数 | 292 条静态(error 268 / warning 24) | — | + +**实测完全达成全覆盖基准**:插件在「工程无配置文件 → 回退内置默认配置」路径下,输出的 292 条静态诊断与模拟基线逐条一致,126 种内置规则全部触发、零解析错误。**ESLint 适配器静态分析链路(规则加载、解析、诊断映射)验证通过。** + +--- + +## 二、逐文件比对(实测 vs 基线) + +| 文件 | 实测 | 基线 | 一致性 | 说明 | +|---|---:|---:|---|---| +| `common.js` | 210 | 210 | ✅ 逐条一致 | core ESLint 规则主样例文件 | +| `typescript.ts` | 65 | 65 | ✅ 逐条一致 | `@typescript-eslint/` 规则主样例文件 | +| `legacysyntax.cjs` | 14 | 14 | ✅ 逐条一致 | 旧式(宽松模式)语法规则样例文件 | +| `esm-demo.js` | 3 | 3 | ✅ 逐条一致 | ESM 相关规则样例文件 | +| **合计** | **292** | **292** | **✅ 零偏差** | — | + +> 比对方法:将插件报告中的每条静态诊断(规则名 + 行号 + 严重级别)与模拟基线做多重集合比对;四文件全部 `missing = 0`、`extra = 0`。 + +--- + +## 三、全覆盖的达成机制 + +126 种内置规则中,有 6 条规则无法在常规 `.js`(module 模式)样例中触发,通过 `legacysyntax.cjs` 单文件解决,机制如下: + +1. **5 条宽松模式规则**(`no-nonoctal-decimal-escape`、`no-octal`、`no-with`、`no-delete-var`、`no-dupe-args`):触发代码(`'\8'` 转义、八进制 `010`、`with`、`delete` 变量、重复参数)在严格模式下本身是 SyntaxError,结构上只可能在宽松(sloppy)模式触发。`.cjs` 文件被 flat config 默认按 `sourceType: "commonjs"`(宽松模式)解析,无需任何配置文件。 +2. **`preserve-caught-error`**:eslint 9.39.4 引擎已实现该规则,但 @eslint/js 9.39.4 的 recommended 未启用(10.0.1 才收录)。通过文件首行 `/* eslint preserve-caught-error: "error" */` 行内注释启用。 + +### 6 条规则验证明细(`legacysyntax.cjs`) + +| 规则 | 实测行号 | 严重级别 | 状态 | +|---|---|---|---| +| `no-nonoctal-decimal-escape` | L8(×2) | error | ✅ 触发 | +| `no-octal` | L11 | error | ✅ 触发 | +| `no-with` | L16 | error | ✅ 触发 | +| `no-delete-var` | L22 | error | ✅ 触发 | +| `no-dupe-args` | L25 | error | ✅ 触发 | +| `preserve-caught-error` | L33 | error | ✅ 触发(行内注释) | + +同文件连带触发的其他规则(与基线一致):`no-undef` L17(×3)/ L36、`no-var` L21、`no-redeclare` L25、`no-unused-vars` L32。 + +--- + +## 四、诊断分布统计 + +**按严重级别**(静态 292 条): + +| 级别 | 数量 | 占比 | +|---|---:|---:| +| error(🔴) | 268 | 91.8% | +| warning(🟡) | 24 | 8.2% | + +**按规则类别**(126 种): + +| 类别 | 种数 | 说明 | +|---|---:|---| +| core ESLint 规则 | 94 | @eslint/js 9.39.4 recommended + 插件精选规则 | +| `@typescript-eslint/` 规则 | 32 | ts.configs.recommended + 插件精选规则 | + +**按文件**:`common.js` 210 · `typescript.ts` 65 · `legacysyntax.cjs` 14 · `esm-demo.js` 3。 + +> 注:插件报告中另有 AI 审查建议 12 条(common.js 4 / legacysyntax.cjs 4 / esm-demo.js 2 / typescript.ts 2),属 AI 增强能力,不计入静态规则覆盖口径。 + +--- + +## 五、实测环境判定 + +实测报告的文件路径与预期部署一致: + +- `data\demo-eslint\src\legacysyntax.cjs`(14 条诊断、无 parse-error → **`.cjs` 宽松模式解析生效,确认插件未发现任何工程配置、正确回退内置配置**) +- `data\demo-eslint\src\`(common.js / esm-demo.js / typescript.ts,诊断数与基线完全相同) + +**配置路径验证**:工程不含任何 `eslint.config.*` 与 `.eslintrc*`,插件按设计走「内置默认配置回退」路径。该路径与工作区打开方式无关(打开工程根或外层目录结果一致),292/292 逐条一致即为最终确认。 + +--- + +## 六、数据口径说明 + +| 项 | 说明 | +|---|---| +| 覆盖率口径 | 126 种 = 插件内置配置实际生效 125 种(@eslint/js 9.39.4 recommended + 精选规则)+ `preserve-caught-error`(由 `.cjs` 首行行内注释启用);纯规则口径,不含 parse-error | +| 比对引擎 | eslint 9.39.4 + @eslint/js 9.39.4 + typescript-eslint 8.60.1(与插件 `package-lock.json` 逐字一致) | +| 基线数据 | `/data/user/work/results/round2-verify.json`(本轮比对明细) | +| 已知口径差异 | 工程自检脚本 `run-coverage.mjs`(用工程自带 @eslint/js 10.0.1)计 294 条:多出的 1 条为 `typescript.ts` L189 `preserve-caught-error`(10.0.1 的 recommended 启用该规则、插件 9.39.4 内置配置未启用),属引擎版本口径差异,非插件缺陷 | +| 版本备注 | 测试机 `typescript.ts` 为 202 行版本(65 条);当前版(212 行)多 1 条 `@typescript-eslint/no-unused-vars` L188,同步与否均不影响 126 种全覆盖结论 | + +--- + +## 七、结论与后续 + +**demo-eslint 工程的插件实测验证完成**: + +1. ✅ 插件行为零偏差(292/292 逐条一致) +2. ✅ 内置配置全覆盖达成(126/126 种规则,零解析错误) +3. ✅ 「内置配置回退」路径完整验证(工程无配置文件的场景) + +**后续建议**(如需继续扩展验证): + +- **其他 3 个工程复测**:`demo-sqlfluff`、`demo-pmd`、`demo-stylelint` 按同样方法(先本地模拟基线 → 实测比对),其中 PMD 注意 Java 版本与 ruleset、SQLFluff 注意 dialect 与 AL06 样例、Stylelint 注意配置优先级。 +- **「工程配置优先」路径补验**(可选):若后续想验证插件发现工程配置的行为,可在工程根添加 `eslint.config.mjs` 并以工程根为第一个工作区文件夹打开,预期插件改用工程配置(诊断会有差异,属正常)。 + +--- + +*报告生成于 2026-08-25 · demo-eslint 插件实测 · 比对脚本:`round2-compare.mjs` · 数据:`round2-verify.json`* diff --git a/data/demo-eslint/reports/esm-demo-review.md b/data/demo-eslint/reports/esm-demo-review.md new file mode 100644 index 0000000..2dfff99 --- /dev/null +++ b/data/demo-eslint/reports/esm-demo-review.md @@ -0,0 +1,33 @@ +# 代码审查报告 + +**文件:** `data\demo-eslint\src\esm-demo.js` +**语言:** javascript +**耗时:** 64.7s +**分析工具:** eslint + +--- + +总计: 5 | 错误: 3 | 警告: 1 | 建议: 1 + +静态分析 · 3 个问题 + +- 🔴 `eslint:no-unused-vars` L7 + 第7行的 'reassignImport' 已定义但从未使用。 + 建议: 该函数未被调用,应删除;如果后续可能使用,请在代码中调用它。建议直接删除整个函数,避免死代码。 +- 🔴 `eslint:no-import-assign` L8 + 第8行 'fs' 是只读的,不能为其赋值。 + 建议: import 导入的绑定是只读的,重新赋值会引发运行时错误。如果函数需要操作 fs,请使用另一个变量保存导入的对象;或删除该赋值语句。由于函数本身未使用,建议删除整个函数。 +- 🔴 `eslint:no-undef` L11 + 第11行 'console' 未定义。 + 建议: console 在浏览器和 Node.js 中通常是全局对象,但 ESLint 环境未配置。请在 .eslintrc 中设置 env.node 或 env.browser,或在代码顶部添加 /* global console */ 注释。或者改用 globalThis.console.log(fs)。 + +AI 审查 · 2 条建议 + +- 🟡 [AI] [design] `dead-code-with-runtime-error` L7 + **死代码中隐藏运行时错误** + reassignImport 函数为 import 绑定 fs 赋值,在 ESM 中这是非法操作,一旦调用会立即抛出 TypeError。虽然当前函数未使用,但保留这种死代码会误导后续维护者,并可能被误调用导致崩溃。 + 建议: 删除整个函数 reassignImport,或将其重构为合法操作(例如读取 fs 的属性)。 +- 🔵 [AI] [design] `module-top-level-side-effect` L11 + **模块顶层执行 console.log 产生副作用** + 第 11 行的 console.log 在模块被导入时立即执行。如果这是被复用的模块,会导致控制台输出,污染调用方,且难以测试。 + 建议: 将 console.log 移到函数内部或通过导出接口提供;若仅是调试用途,在提交前移除。 diff --git a/data/demo-eslint/reports/legacysyntax-review.md b/data/demo-eslint/reports/legacysyntax-review.md new file mode 100644 index 0000000..2cde3a7 --- /dev/null +++ b/data/demo-eslint/reports/legacysyntax-review.md @@ -0,0 +1,74 @@ +# 代码审查报告 + +**文件:** `data\demo-eslint\src\legacysyntax.cjs` +**语言:** javascript +**耗时:** 152.1s +**分析工具:** eslint + +--- + +总计: 18 | 错误: 14 | 警告: 2 | 建议: 2 + +静态分析 · 14 个问题 + +- 🔴 `eslint:no-nonoctal-decimal-escape` L8 + 不要使用 '\8' 转义序列。 + 建议: 在非严格模式字符串中 `\8` 会被静默当作普通字符 `8`。如果只是要字符 8,请直接写 `'8 and 9'`;如果要表示反斜杠加数字,请使用 `String.raw` 或双反斜杠转义。 +- 🔴 `eslint:no-nonoctal-decimal-escape` L8 + 不要使用 '\9' 转义序列。 + 建议: 在非严格模式字符串中 `\9` 会被静默当作普通字符 `9`。如果只是要字符 9,请直接写 `'8 and 9'`;如果要表示反斜杠加数字,请使用 `String.raw` 或双反斜杠转义。 +- 🔴 `eslint:no-octal` L11 + 不应使用旧式八进制字面量。 + 建议: `010` 是旧式八进制字面量,值为十进制 8,且在严格模式下是语法错误。若要表示八进制值 8,应写 `0o10`;若要表示十进制 10,应写 `10`。 +- 🔴 `eslint:no-with` L16 + 不应使用 `with` 语句。 + 建议: `with` 会动态修改作用域链,难以静态分析和优化,在严格模式或 ESM 中还是语法错误。建议先解构出所需属性:`const { pi, e } = { pi: 3.14, e: 2.72 };` 再使用。 +- 🔴 `eslint:no-undef` L17 + 未定义变量 `console`。 + 建议: ESLint 配置中没有声明 `console` 全局变量。建议在 `eslint.config.mjs` 中设置 `languageOptions.globals` 为 `globals.node`,或在本文件中使用 `globalThis.console`。 +- 🔴 `eslint:no-undef` L17 + 未定义变量 `pi`。 + 建议: `with` 对象中的属性 `pi` 不会成为真正的词法作用域变量,因此静态分析无法识别。请参考 `no-with` 的修复:先解构出 `pi` 再使用。 +- 🔴 `eslint:no-undef` L17 + 未定义变量 `e`。 + 建议: `with` 对象中的属性 `e` 不会成为真正的词法作用域变量,因此静态分析无法识别。请参考 `no-with` 的修复:先解构出 `e` 再使用。 +- 🔴 `eslint:no-var` L21 + 不应使用 `var`,请改用 `let` 或 `const`。 + 建议: `deletable` 后续会被置空,使用 `let deletable = 42;`;如果确定不再重新赋值,可考虑 `const`。 +- 🔴 `eslint:no-delete-var` L22 + 不应删除变量。 + 建议: `delete` 只能删除对象属性,不能移除 `var`/`let`/`const` 声明的变量绑定。若要清理引用,请改为 `deletable = null;`;若要真正可删除,请把数据放进对象属性再 `delete`。 +- 🔴 `eslint:no-dupe-args` L25 + 函数参数名重复:`a`。 + 建议: 重命名第二个参数,并保持函数行为不变:`function duplicateArgs(a, b) { return b; }`。 +- 🔴 `eslint:no-redeclare` L25 + `a` 已重复声明。 + 建议: 移除重复参数名,将第二个参数改名为 `b`,并返回 `b`。 +- 🔴 `eslint:no-unused-vars` L32 + 变量 `err` 已被声明但从未使用。 + 建议: 如果要保留原始错误,请把它作为 `cause` 传给新错误:`throw new Error('parse failed', { cause: err });`;如果确实不需要原始错误,可使用省略绑定参数的 `catch { ... }`。 +- 🔴 `eslint:preserve-caught-error` L33 + 抛出的新错误没有附带原始错误作为 `cause`。 + 建议: 在抛出 `Error` 时使用第二个 options 参数传递 `cause`:`throw new Error('parse failed', { cause: err });`,以保留原始错误链和堆栈信息。 +- 🔴 `eslint:no-undef` L36 + 未定义变量 `console`。 + 建议: 在 ESLint 配置中声明 `console` 全局变量,或使用 `globalThis.console.log(...)`。 + +AI 审查 · 4 条建议 + +- 🟡 [AI] [bug] `legacy-octal-value-confusion` L11 + **旧式八进制字面量 `010` 的实际值是 8,而非 10** + 在宽松模式下,`010` 会被解析为旧式八进制字面量,等于十进制 8。如果写 `010` 的人以为是十进制 10,就会引入静默的数值错误;即使确实想表示八进制,也应使用明确的 `0o10`,避免歧义并绕过 `no-octal` 规则。 + 建议: 若语义是十进制 10,改为 `10`;若语义是八进制 8,改为 `0o10`。 +- 🟡 [AI] [bug] `delete-var-no-op` L22 + **`delete deletable` 不会删除变量,清理意图不生效** + `delete` 操作符只对对象属性有意义;对 `var` 声明的标识符执行 `delete` 不会移除该绑定,在非严格模式下通常静默失败并返回 `false`,`deletable` 仍然是 42。这样的代码会给维护者造成“变量已被删除”的错觉。 + 建议: 如果只想清空引用,改为 `deletable = null;`;如果需要“可删除”的数据,请改为 `const data = { deletable: 42 };` 然后 `delete data.deletable;`。 +- 🔵 [AI] [design] `legacy-syntax-in-production-fixture` L3 + **演示旧式语法的代码不应混入生产/业务源码** + 该文件故意依靠 `.cjs` 和 sloppy mode 来使用 `with`、旧式八进制、重复参数等旧式语法。一旦文件被加入 `"use strict"`,或迁移为 ESM/被复制进严格模式环境,这些代码会立即变成 `SyntaxError`。作为语法规则演示/回归样例,应放在测试 fixtures 中并由 ESLint 显式配置解析。 + 建议: 将这类 legacy 语法样例移动到 `tests/fixtures/legacy/`,在测试配置中明确 `sourceType: 'script'` 和适用的 `ecmaVersion`,不要放在主代码目录。 +- 🔵 [AI] [design] `missing-node-globals-config` L17 + **ESLint 未声明 Node 全局变量,导致 `console` 反复触发 `no-undef`** + 第 17、36 行的 `console` 在 Node 运行时是合法的全局变量,但当前 ESLint 配置没有引入 `globals.node` 或文件级注释,所以被重复报告为未定义。逐个改成 `globalThis.console` 会掩盖根因:ESLint 环境配置缺失。 + 建议: 在 `eslint.config.mjs` 中 `languageOptions.globals` 引入 `globals.node`;或在本文件顶部增加 `/* global console */`。 diff --git a/data/demo-eslint/reports/typescript-review.md b/data/demo-eslint/reports/typescript-review.md new file mode 100644 index 0000000..fde8512 --- /dev/null +++ b/data/demo-eslint/reports/typescript-review.md @@ -0,0 +1,219 @@ +# 代码审查报告 + +**文件:** `data\demo-eslint\src\typescript.ts` +**语言:** typescript +**耗时:** 257.3s +**分析工具:** eslint + +--- + +总计: 67 | 错误: 57 | 警告: 10 | 建议: 0 + +静态分析 · 65 个问题 + +- 🔴 `eslint:@typescript-eslint/triple-slash-reference` L5 + 不要对 ./typesmodule.ts 使用三斜线引用,请改用 import 风格。 + 建议: 删除这一行;若必须引入副作用,使用 `import './typesmodule';`。 +- 🔴 `eslint:@typescript-eslint/consistent-type-imports` L8 + 声明中的所有导入都只被用作类型,请使用 `import type`。 + 建议: 将第 8 行改为 `import type { DemoType } from './typesmodule';`。 +- 🔴 `eslint:@typescript-eslint/no-unused-vars` L11 + 'legacyLoad' 已定义但从未被使用。 + 建议: 删除该函数,或将其加入底部 export 列表 / 添加 `export` 前缀。 +- 🔴 `eslint:no-var` L12 + 检测到 var,建议使用 let 或 const。 + 建议: 将 `var mod` 改为 `const mod`,并考虑用 ES import 替代 require。 +- 🔴 `eslint:@typescript-eslint/no-require-imports` L12 + 禁止使用 require 风格导入。 + 建议: 移除 require,改用 ES module:在文件顶部 `import * as mod from './typesmodule';`,并在函数中返回 `mod`。 +- 🔴 `eslint:@typescript-eslint/no-unused-vars` L17 + 'arrayConstructor' 已定义但从未被使用。 + 建议: 删除函数,或添加 `export` 前缀导出。 +- 🔴 `eslint:@typescript-eslint/no-array-constructor` L18 + 建议优先使用数组字面量 []。 + 建议: 用 `[1, 2, 3]` 代替 `new Array(1, 2, 3)`。 +- 🔴 `eslint:@typescript-eslint/no-dynamic-delete` L23 + 不要动态删除计算属性键。 + 建议: 改用 `Map` 并通过 `map.delete(key)` 删除;如果必须使用对象,只能删除字面量属性或使用 `Reflect.deleteProperty` 并明确其风险。 +- 🔴 `eslint:@typescript-eslint/no-unused-vars` L27 + 'UtilityClass' 已定义但从未被使用。 + 建议: 导出该类或删除它。 +- 🔴 `eslint:@typescript-eslint/no-unused-vars` L34 + 'invalidVoid' 已定义但从未被使用。 + 建议: 删除函数或添加 `export` 前缀。 +- 🔴 `eslint:@typescript-eslint/no-non-null-assertion` L41 + 禁止非空断言。 + 建议: 删除 `!`,让 null/undefined 交还给类型系统处理。 +- 🔴 `eslint:@typescript-eslint/no-non-null-asserted-nullish-coalescing` L41 + 空值合并运算符本身就是为处理 null/undefined 设计的,使用非空断言没有意义。 + 建议: 直接写 `return value ?? 'fallback';`。 +- 🔴 `eslint:@typescript-eslint/no-non-null-assertion` L46 + 禁止非空断言。 + 建议: 不要在 `value` 上使用 `!`;如果值为 null,请先判空。 +- 🔴 `eslint:@typescript-eslint/no-non-null-assertion` L46 + 禁止非空断言。 + 建议: 去掉非空断言并做空值保护。 +- 🔴 `eslint:@typescript-eslint/no-extra-non-null-assertion` L46 + 禁止多余的非空断言。 + 建议: 至多保留一个 `!`(最好不用),并改为 `value?.length ?? 0`。 +- 🔴 `eslint:@typescript-eslint/no-unused-vars` L50 + 'optionalChainAsserted' 已定义但从未被使用。 + 建议: 删除函数或添加 `export` 前缀。 +- 🔴 `eslint:@typescript-eslint/no-non-null-assertion` L51 + 禁止非空断言。 + 建议: 移除可选链后的 `!`,改用 `??` 提供默认值。 +- 🔴 `eslint:@typescript-eslint/no-non-null-asserted-optional-chain` L51 + 可选链表达式本来就可能返回 undefined,使用非空断言既不安全也错误。 + 建议: 删除 `!`,通过 `??` 或条件判断处理 undefined。 +- 🔴 `eslint:@typescript-eslint/no-unused-vars` L55 + 'thisAlias' 已定义但从未被使用。 + 建议: 删除函数或添加 `export` 前缀。 +- 🔴 `eslint:@typescript-eslint/no-this-alias` L56 + 不允许将 this 赋值给局部变量。 + 建议: 直接访问 `this.x`;若在嵌套函数中需要 this,改用箭头函数或显式传入上下文对象。 +- 🔴 `eslint:@typescript-eslint/no-unused-vars` L62 + 'shadowed' 被赋值后从未使用。 + 建议: 删除外层未使用的变量,或改成 `void shadowed;` 进行使用。 +- 🔴 `eslint:@typescript-eslint/no-shadow` L64 + 'shadowed' 已在第 62 行第 9 列的上层作用域中声明。 + 建议: 将内部变量重命名,避免遮蔽外层变量。 +- 🔴 `eslint:@typescript-eslint/no-unused-vars` L71 + 'preferConstInTs' 已定义但从未被使用。 + 建议: 删除函数或添加 `export` 前缀。 +- 🔴 `eslint:prefer-const` L72 + 'fixed' 从未被重新赋值,建议使用 const。 + 建议: 把 `let fixed` 改为 `const fixed`。 +- 🔴 `eslint:@typescript-eslint/no-unused-vars` L77 + 'EmptyConstructor' 已定义但从未被使用。 + 建议: 删除类或添加 `export` 前缀。 +- 🔴 `eslint:@typescript-eslint/no-useless-empty-export` L82 + 空的 export 没有任何作用,可以删除。 + 建议: 删除 `export {};`,因为文件已有其他导出。 +- 🔴 `eslint:@typescript-eslint/no-unused-vars` L85 + 'UninitializedEnum' 已定义但从未被使用。 + 建议: 删除枚举或添加 `export` 前缀。 +- 🔴 `eslint:@typescript-eslint/no-unused-vars` L89 + 'DuplicateEnum' 已定义但从未被使用。 + 建议: 删除枚举或添加 `export` 前缀。 +- 🔴 `eslint:@typescript-eslint/no-duplicate-enum-values` L91 + 重复的枚举成员值 1。 + 建议: 将 B 改为不同值,如 `B = 2`。 +- 🔴 `eslint:@typescript-eslint/no-unused-vars` L96 + 'NonLiteralEnum' 已定义但从未被使用。 + 建议: 删除枚举或添加 `export` 前缀。 +- 🔴 `eslint:@typescript-eslint/unified-signatures` L102 + 这些重载可以合并成一个签名 `string | 'x'`。 + 建议: 合并重载;由于 `'x'` 是 `string` 的子类型,直接删除第二个重载即可。 +- 🔴 `eslint:@typescript-eslint/no-explicit-any` L108 + 意外的 any 类型,请指定具体类型。 + 建议: 将参数类型改为泛型或 `unknown` 后收窄。 +- 🔴 `eslint:@typescript-eslint/no-explicit-any` L108 + 意外的 any 类型,请指定具体类型。 + 建议: 将返回值类型改为泛型或具体类型。 +- 🔴 `eslint:@typescript-eslint/no-empty-object-type` L113 + `{}` 空对象类型允许任意非空值,包括字面量 `0` 和 `""`。如需保留此行为,请通过行内注释禁用该规则或配置 allowObjectTypes;如需表示任意对象,应使用 `object`;如需表示任意值,应使用 `unknown`。 + 建议: 将 `EmptyObject` 定义为 `type EmptyObject = object;` 或 `unknown`。 +- 🔴 `eslint:@typescript-eslint/no-empty-object-type` L114 + 空接口声明允许任意非空值,包括字面量 `0` 和 `""`。如需保留此行为,请通过行内注释禁用该规则或配置 allowInterfaces;如需表示任意对象,应使用 `object`;如需表示任意值,应使用 `unknown`。 + 建议: 将空接口改为 `type EmptyInterface = object;`(若想让任意对象类型)或 `unknown`。 +- 🔴 `eslint:@typescript-eslint/no-unused-vars` L114 + 'EmptyInterface' 已定义但从未被使用。 + 建议: 删除接口或添加 `export` 前缀(改为 `export type EmptyInterface = object;` 可同时解决空类型问题)。 +- 🔴 `eslint:@typescript-eslint/no-unused-vars` L115 + 'emptyObj' 被赋值后从未使用。 + 建议: 删除变量或添加 `export` 前缀,或使用 `void emptyObj;` 标记为有意使用。 +- 🔴 `eslint:@typescript-eslint/no-wrapper-object-types` L118 + 建议使用原始类型 `string` 作为类型名,而不是大写包装类型 `String`。 + 建议: 将参数类型 `String` 改为 `string`。 +- 🔴 `eslint:@typescript-eslint/no-wrapper-object-types` L118 + 建议使用原始类型 `number` 作为类型名,而不是大写包装类型 `Number`。 + 建议: 将参数类型 `Number` 改为 `number`。 +- 🔴 `eslint:@typescript-eslint/no-wrapper-object-types` L118 + 建议使用原始类型 `boolean` 作为类型名,而不是大写包装类型 `Boolean`。 + 建议: 将参数类型 `Boolean` 改为 `boolean`。 +- 🔴 `eslint:@typescript-eslint/no-unsafe-function-type` L123 + `Function` 类型可以接受任意函数式值,建议显式定义函数参数和返回类型。 + 建议: 将 `UnsafeFn` 定义为明确的函数签名,如 `type UnsafeFn = (...args: unknown[]) => unknown;`。 +- 🔴 `eslint:@typescript-eslint/no-unused-expressions` L130 + 期望赋值或函数调用,但看到的是表达式。 + 建议: 删除无副作用的 `flag && value;`,或改成 `if (flag) { return value; }`。 +- 🔴 `eslint:@typescript-eslint/no-unused-expressions` L131 + 期望赋值或函数调用,但看到的是表达式。 + 建议: 删除 `value;` 语句,或改为 `return value;`。 +- 🔴 `eslint:@typescript-eslint/no-unnecessary-type-constraint` L135 + 将泛型 `T` 约束为 `unknown` 没有任何作用,是不必要的。 + 建议: 移除 `extends unknown` 约束,直接写成 ``。 +- 🔴 `eslint:@typescript-eslint/no-unsafe-declaration-merging` L140 + 类与接口之间的声明合并是不安全的。 + 建议: 不要在类与接口间做同名合并;将成员直接定义在类中。 +- 🔴 `eslint:@typescript-eslint/no-unused-vars` L140 + 'MergedThing' 已定义但从未被使用。 + 建议: 删除未使用声明,或添加 `export` 前缀。 +- 🔴 `eslint:@typescript-eslint/no-unsafe-declaration-merging` L143 + 类与接口之间的声明合并是不安全的。 + 建议: 同上,消除同名合并,并在类中实现方法。 +- 🔴 `eslint:@typescript-eslint/no-unused-vars` L148 + 'tsIgnored' 已定义但从未被使用。 + 建议: 删除函数或添加 `export` 前缀。 +- 🔴 `eslint:@typescript-eslint/ban-ts-comment` L149 + 请使用 `@ts-expect-error` 代替 `@ts-ignore`,因为 `@ts-ignore` 在下一行没有错误时不会报错。 + 建议: 将 `@ts-ignore` 改为 `@ts-expect-error`,或修复下一行的类型错误后删除注释。 +- 🔴 `eslint:@typescript-eslint/no-unused-vars` L155 + 'MisusedNew' 已定义但从未被使用。 + 建议: 删除接口或添加 `export` 前缀。 +- 🔴 `eslint:@typescript-eslint/no-misused-new` L156 + 接口不能被实例化,只有类可以。 + 建议: 将构造签名的定义放到 `class` 中,而不是 `interface`。 +- 🔴 `eslint:@typescript-eslint/no-misused-new` L157 + 接口不能被实例化,只有类可以。 + 建议: 不要在接口中声明 `constructor`,改写为类的构造器。 +- 🔴 `eslint:@typescript-eslint/no-namespace` L161 + ES2015 模块语法优于命名空间。 + 建议: 将 `namespace LegacyNamespace { ... }` 改为普通对象/ES module 导出。 +- 🔴 `eslint:@typescript-eslint/prefer-namespace-keyword` L164 + 请使用 `namespace` 而不是 `module` 来声明自定义 TypeScript 模块。 + 建议: 将 `module LegacyModule` 改为 `namespace LegacyModule`(但更推荐 ES module 语法)。 +- 🔴 `eslint:@typescript-eslint/no-namespace` L164 + ES2015 模块语法优于命名空间。 + 建议: 将 `module LegacyModule` 改为普通对象/ES module 导出,避免模块命名空间。 +- 🔴 `eslint:@typescript-eslint/prefer-as-const` L170 + 期望使用 const 断言,而不是字面量类型注解。 + 建议: 改为 `const literal = 'hello' as const;`。 +- 🟡 `eslint:@typescript-eslint/no-extraneous-class` L27 + 类中只有静态属性/方法,不应使用类。 + 建议: 改用普通对象:`export const UtilityClass = { helper: (): number => 42 };`。 +- 🟡 `eslint:@typescript-eslint/no-invalid-void-type` L34 + void 不能作为联合类型的成员。 + 建议: 移除联合中的 void,改为返回 `undefined` 或直接使用 `: void`;若函数有返回值,使用具体类型。 +- 🟡 `eslint:@typescript-eslint/no-extraneous-class` L77 + 类中只有一个空的构造器,不应使用类。 + 建议: 删除整个类;若需要实例化对象,写成普通对象或函数。 +- 🟡 `eslint:@typescript-eslint/no-useless-constructor` L78 + 无用的构造器。 + 建议: 直接移除 `constructor() {}`,让类使用默认构造器。 +- 🟡 `eslint:@typescript-eslint/prefer-enum-initializers` L86 + 成员 'First' 的值应显式定义。 + 建议: 显式赋值,如 `First = 0`。 +- 🟡 `eslint:@typescript-eslint/prefer-enum-initializers` L87 + 成员 'Second' 的值应显式定义。 + 建议: 显式赋值,如 `Second = 1`。 +- 🟡 `eslint:@typescript-eslint/prefer-literal-enum-member` L97 + 显式枚举值只能是字符串或数字字面量。 + 建议: 将 `A = someValue` 改为 `A = 10`。 +- 🟡 `eslint:prefer-rest-params` L176 + 请使用剩余参数代替 `arguments`。 + 建议: 改为 `function restParams(...args: unknown[]) { return args; }`。 +- 🟡 `eslint:prefer-spread` L181 + 请使用展开运算符代替 `.apply()`。 + 建议: 改为 `return mySpreadFn(...base);`。 + +AI 审查 · 2 条建议 + +- 🔴 [AI] [bug] `missing-method-in-declaration-merging` L143 + **接口与类合并后 method 缺少实现** + MergedThing 接口通过声明合并为同名类实例类型增加了 method(): void 签名,但类中没有实现该方法。因此类型检查允许 new MergedThing().method(),运行时会抛出 method is not a function 的 TypeError。静态规则只报告了合并不安全,没有指出缺少实现的问题。 + 建议: 删除 interface MergedThing,并将 method 的实现写到 class MergedThing 中;如果确实需要接口合并,也要在类中提供对应实现。 +- 🟡 [AI] [design] `any-return-from-require` L12 + **legacyLoad 隐式返回 any 类型** + require('./typesmodule') 的返回值类型为 any,函数没有显式返回类型,因此 legacyLoad 的返回类型被推断为 any,调用方会丢失类型检查。该类型泄漏不在 no-require-imports 规则的直接范围内。 + 建议: 改用 ES module 导入并声明返回类型,例如删除 var mod = require(...),在文件顶部使用 import * as mod from './typesmodule';,并让函数返回 typeof mod。 diff --git a/data/demo-eslint/run-coverage.mjs b/data/demo-eslint/run-coverage.mjs new file mode 100644 index 0000000..7fcbab3 --- /dev/null +++ b/data/demo-eslint/run-coverage.mjs @@ -0,0 +1,166 @@ +// ESLint 覆盖率测试 v3:复刻内置配置(与 ESLintAdapter.getDefaultConfig 逐字一致) +// + legacysyntax.js script 模式(对应 demo 工程配置 eslint.config.mjs) +// + 以 calculateConfigForFile 计算各文件类型实际生效(severity != off)的规则集 +import { ESLint } from 'eslint'; +import js from '@eslint/js'; +import ts from 'typescript-eslint'; +import { readFileSync, readdirSync, writeFileSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const SRC = join(__dirname, 'src'); + +// ===== 以下与 src/rules/builtin-rules.ts 逐字一致 ===== +const eslintExtraRules = { + 'eqeqeq': 'error', 'no-eq-null': 'error', 'no-self-compare': 'error', + 'no-promise-executor-return': 'error', 'no-shadow': 'error', 'no-unassigned-vars': 'error', + 'no-useless-assignment': 'error', 'block-scoped-var': 'error', 'default-case': 'error', + 'default-case-last': 'error', 'no-unmodified-loop-condition': 'error', 'no-unreachable-loop': 'error', + 'no-eval': 'error', 'no-extend-native': 'error', 'no-var': 'error', + 'no-await-in-loop': 'warn', 'prefer-template': 'warn', 'prefer-object-spread': 'warn', + 'prefer-rest-params': 'warn', 'prefer-spread': 'warn', 'prefer-object-has-own': 'warn', + 'no-useless-concat': 'warn', 'no-useless-return': 'warn', 'no-useless-computed-key': 'warn', + 'no-useless-rename': 'warn', 'no-param-reassign': 'warn', 'no-return-assign': 'error', + 'no-throw-literal': 'error', 'camelcase': 'warn', 'new-cap': 'warn', 'no-array-constructor': 'error', +}; + +const eslintExtraTsRules = { + '@typescript-eslint/no-non-null-assertion': 'error', + '@typescript-eslint/no-dynamic-delete': 'error', + '@typescript-eslint/no-useless-empty-export': 'error', + '@typescript-eslint/consistent-type-imports': 'error', + '@typescript-eslint/unified-signatures': 'error', + '@typescript-eslint/no-extraneous-class': 'warn', + '@typescript-eslint/no-useless-constructor': 'warn', + '@typescript-eslint/no-non-null-asserted-nullish-coalescing': 'error', + '@typescript-eslint/no-invalid-void-type': 'warn', + '@typescript-eslint/prefer-literal-enum-member': 'warn', + '@typescript-eslint/prefer-enum-initializers': 'warn', + 'no-shadow': 'off', + '@typescript-eslint/no-shadow': 'error', + 'no-array-constructor': 'off', +}; + +const TS_FILES = ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts']; +const JS_FILES = ['**/*.js', '**/*.jsx', '**/*.mjs', '**/*.cjs']; + +const tsConfigs = ts.configs.recommended.map(cfg => ({ + ...cfg, + files: (cfg).files ?? TS_FILES, +})); + +// 复刻内置默认配置(与 ESLintAdapter.getDefaultConfig 一致)+ legacysyntax script 模式 +const defaultConfig = [ + js.configs.recommended, + { files: JS_FILES, languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } } }, + ...tsConfigs, + { rules: eslintExtraRules }, + { files: TS_FILES, rules: eslintExtraTsRules }, + { files: ['**/legacysyntax.js'], languageOptions: { sourceType: 'script' } }, +]; + +const files = readdirSync(SRC).filter(f => /\.(js|ts|mjs|cjs)$/.test(f)); +const engine = new ESLint({ cwd: SRC, overrideConfigFile: true, overrideConfig: defaultConfig }); + +// ===== 实际生效规则(severity != off)===== +async function effectiveRules(sampleFile) { + const cfg = await engine.calculateConfigForFile(join(SRC, sampleFile)); + const out = new Set(); + for (const [name, val] of Object.entries(cfg.rules ?? {})) { + const sev = Array.isArray(val) ? val[0] : val; + if (sev !== 'off' && sev !== 0) out.add(name); + } + return out; +} + +const jsEnabled = await effectiveRules('common.js'); +const tsEnabled = await effectiveRules('typescript.ts'); +const builtinAll = new Set([...jsEnabled, ...tsEnabled]); + +// ===== 逐文件 lint ===== +const actualByFile = {}; +const parseErrors = {}; +let violationTotal = 0; +for (const f of files) { + const text = readFileSync(join(SRC, f), 'utf8'); + const results = await engine.lintText(text, { filePath: join(SRC, f) }); + const rulesHit = new Set(); + for (const r of results) { + for (const m of r.messages) { + if (m.fatal) { parseErrors[f] = m.message; continue; } + violationTotal++; + if (m.ruleId) rulesHit.add(m.ruleId); + } + } + actualByFile[f] = rulesHit; +} + +const actualRaw = new Set(); +for (const s of Object.values(actualByFile)) for (const r of s) actualRaw.add(r); + +// ===== 期望规则提取(demo 注释标注)===== +const NOISE = new Set(['eslint', '本文件', '说明', '保留', '合并', '用于', '异步', '比较', '该文件', 'eslint.config.mjs', '顺手在此以', '文件', '演示', '规则', + // 注释噪音:文件名 / 指令 / 未启用规则名 / 笔误(真实规则为 no-unused-vars) + 'esm-demo', 'ts-ignore', 'no-empty-function', 'no-useless-vars']); +const expectByFile = {}; +for (const f of files) { + const text = readFileSync(join(SRC, f), 'utf8'); + const isTs = /\.ts$/.test(f); + const expects = new Set(); + for (const line of text.split('\n')) { + const m = line.match(/^\s*\/\/\s*(.+)$/); + if (!m) continue; + const body = m[1]; + // 候选 token:kebab-case 规则名 或 @typescript-eslint/ 前缀规则名 + for (const tm of body.matchAll(/(@typescript-eslint\/[a-z0-9-]+|[a-z][a-z0-9]*(-[a-z0-9]+)+)/g)) { + const name = tm[1]; + if (NOISE.has(name)) continue; + if (!isTs && name.startsWith('@typescript-eslint/')) continue; + expects.add(name); + } + } + expectByFile[f] = expects; +} +const expectAll = new Set(); +for (const s of Object.values(expectByFile)) for (const r of s) expectAll.add(r); + +// ===== 统计 ===== +// 名称归一化:注释里通常省略 @typescript-eslint/ 前缀,比较时两边都剥掉前缀 +const normalize = r => r.replace(/^@typescript-eslint\//, ''); +const actualNorm = new Set([...actualRaw].map(normalize)); +const jsHit = [...jsEnabled].filter(r => actualRaw.has(r)); +const tsHit = [...tsEnabled].filter(r => actualRaw.has(r)); +const hitInBuiltin = [...builtinAll].filter(r => actualRaw.has(r)); +const expectHit = [...expectAll].filter(r => actualNorm.has(normalize(r))); +const expectMissed = [...expectAll].filter(r => !actualNorm.has(normalize(r))).sort(); + +console.log('=== ESLint 覆盖率实测结果(v3,生效口径)==='); +console.log(`测试文件: ${files.join(', ')}`); +console.log(`解析错误: ${Object.keys(parseErrors).length ? JSON.stringify(parseErrors) : '无'}`); +console.log(`JS 生效规则: ${jsEnabled.size} 条,命中 ${jsHit.length} = ${(jsHit.length / jsEnabled.size * 100).toFixed(1)}%`); +console.log(`TS 生效规则: ${tsEnabled.size} 条,命中 ${tsHit.length} = ${(tsHit.length / tsEnabled.size * 100).toFixed(1)}%`); +console.log(`合并生效规则: ${builtinAll.size} 条,命中 ${hitInBuiltin.length} = ${(hitInBuiltin.length / builtinAll.size * 100).toFixed(1)}%`); +console.log(`demo 期望规则: ${expectAll.size} 条,命中 ${expectHit.length}`); +console.log(`违规总数: ${violationTotal}`); +console.log(`\nJS 未命中 (${jsEnabled.size - jsHit.length}): ${[...jsEnabled].filter(r => !actualRaw.has(r)).sort().join(', ') || '无'}`); +console.log(`\nTS 未命中 (${tsEnabled.size - tsHit.length}): ${[...tsEnabled].filter(r => !actualRaw.has(r)).sort().join(', ') || '无'}`); +console.log(`\n期望未达成: ${expectMissed.join(', ') || '无'}`); + +writeFileSync('/data/user/work/results/eslint_result.json', JSON.stringify({ + tool: 'eslint', + builtinJsTotal: jsEnabled.size, + builtinTsTotal: tsEnabled.size, + builtinAllTotal: builtinAll.size, + jsHit: jsHit.sort(), + tsHit: tsHit.sort(), + jsMissed: [...jsEnabled].filter(r => !actualRaw.has(r)).sort(), + tsMissed: [...tsEnabled].filter(r => !actualRaw.has(r)).sort(), + actualHitRaw: [...actualRaw].sort(), + expectTotal: expectAll.size, + expectHit: expectHit.sort(), + expectMissed, + violationTotal, + parseErrors, +}, null, 2)); +console.log('\n结果已写入 /data/user/work/results/eslint_result.json'); diff --git a/data/demo-eslint/src/common.js b/data/demo-eslint/src/common.js new file mode 100644 index 0000000..313cc58 --- /dev/null +++ b/data/demo-eslint/src/common.js @@ -0,0 +1,608 @@ +// ============ common.js(合并文件) ============ +// 由 ESLint 演示工程原 10 个 JS 文件合并压缩而来,用于触发各条 ESLint 规则。 +// 保留独立文件:legacysyntax.js(script 模式解析)、typescript.ts / typesmodule.ts(TS 规则)。 +// 注:本文件为普通 script(无 import/export),且不含严格模式下的致命语法样例 +//(with / delete 局部变量 / 重复形参 / 顶层重复 let 等),确保 ESLint 能完整解析并触发全部规则。 + +// ===== 原 async.js ===== +// ============ async.js ============ +// 异步 / Promise 相关规则 + +// require-yield +function* generatorWithoutYield() { + console.log('no yield here'); +} + +// no-async-promise-executor +const asyncExecutor = new Promise(async (resolve, reject) => { + resolve(1); +}); + +// no-promise-executor-return +const executorReturn = new Promise((resolve, reject) => { + return resolve(1); +}); + +// no-await-in-loop +async function awaitInLoop(items, url) { + const results = []; + for (const item of items) { + const res = await fetch(url + item); + results.push(res); + } + return results; +} + +// no-return-assign +function returnAssign(x) { + let assignedInReturn; + return assignedInReturn = x; +} + +// no-throw-literal +function throwLiteral() { + throw 'not an error object'; +} + +// no-useless-return +function uselessReturn() { + doSomething(); + return; +} + +function doSomething() { + console.log('work'); +} + +// ===== 原 classes.js ===== +// ============ classes.js ============ +// 类相关规则 + +// constructor-super +class BadConstructor extends Error { + constructor(message) { + console.log(message); + } +} + +// no-class-assign +class Widget {} +Widget = 'overwritten'; + +// no-dupe-class-members +class DupeMember { + method() { + return 1; + } + method() { + return 2; + } +} + +// no-empty-static-block +class EmptyStatic { + static {} +} + +// no-setter-return +class SetterReturn { + set value(v) { + this._value = v; + return v; + } +} + +// getter-return +class GetterNoReturn { + get value() { + const x = 1; + } +} + +// no-this-before-super +class ThisBeforeSuper extends Error { + constructor(message) { + this.message = message; + super(message); + } +} + +// no-unused-private-class-members +class UnusedPrivate { + #secret = 42; + reveal() { + return 'no secret'; + } +} + +// ===== 原 comparison.js ===== +// ============ comparison.js ============ +// 比较运算类规则 + +// eqeqeq +function looseEquality(a, b) { + return a == b; +} + +// no-eq-null +function nullEquality(x) { + return x == null; +} + +// no-self-compare +function selfCompare(x) { + return x === x; +} + +// no-compare-neg-zero +function negZero(x) { + return x === -0; +} + +// use-isnan +function isNanWrong(x) { + return x === NaN; +} + +// valid-typeof +function typeofCheck(x) { + return typeof x === 'strnig'; +} + +// no-constant-binary-expression +function constBinary() { + const y = 'a' + -'b'; + return y; +} + +// no-unexpected-multiline already in syntax + +// no-constant-condition already in syntax + +// no-unsafe-negation already in syntax + +// no-constant-binary-expression already covered + +// ===== 原 control.js ===== +// ============ control.js ============ +// 流程控制 / switch 相关规则 + +// no-fallthrough +function fallthrough(x) { + switch (x) { + case 1: + console.log('one'); + case 2: + console.log('two'); + break; + default: + break; + } +} + +// no-case-declarations +function caseDeclarations(x) { + switch (x) { + case 1: + let caseLocal = 10; + console.log(caseLocal); + break; + default: + break; + } +} + +// no-duplicate-case +function duplicateCase(x) { + switch (x) { + case 1: + break; + case 1: + break; + default: + break; + } +} + +// no-dupe-else-if +function dupeElseIf(x) { + if (x === 1) { + return 'a'; + } else if (x === 1) { + return 'b'; + } + return 'c'; +} + +// default-case +function noDefault(x) { + switch (x) { + case 1: + return 'one'; + } + return 'none'; +} + +// default-case-last +function defaultNotLast(x) { + switch (x) { + default: + return 'default'; + case 1: + return 'one'; + } +} + +// no-constant-condition covered in syntax + +// ===== 原 functions.js ===== +// ============ functions.js ============ +// 函数相关规则 + +// no-func-assign +function assignableFn() { + return 'original'; +} +assignableFn = function () { + return 'reassigned'; +}; + +// no-return-assign covered in async.js +// no-throw-literal covered in async.js + +// no-useless-vars? no such rule + +// for-direction +function forDirection() { + for (let i = 0; i < 10; i--) { + console.log(i); + } +} + +// ===== 原 modern.js ===== +// ============ modern.js ============ +// 现代语法 / 风格偏好规则 + +// camelcase +const my_snake_case_var = 1; + +// new-cap +const generated = new object(); + +// no-useless-concat +const uselessConcat = 'foo' + 'bar'; + +// no-useless-computed-key +const obj = { ['key']: 'value' }; + +// no-dupe-keys +const dupeKeyObj = { a: 1, a: 2 }; + +// no-useless-rename +const { a: a } = someObj; + +// no-var covered in variables.js + +// prefer-template +function preferTemplate(name) { + return 'Hello, ' + name + '!'; +} + +// prefer-object-spread +function preferSpreadStyle() { + const merged = Object.assign({}, { a: 1 }, { b: 2 }); + return merged; +} + +// prefer-rest-params +function preferRest() { + const args = Array.prototype.slice.call(arguments); + return args; +} + +// prefer-spread +function spreadThis() { + return 1; +} +function preferSpreadCall() { + const nums = [1, 2, 3]; + return spreadThis.apply(null, nums); +} + +// prefer-object-has-own +function hasOwnPrefer(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); +} + +// prefer-const +function preferConstDemo() { + let x = 1; + return x; +} + +const someObj = { a: 1, b: 2, key: 'v' }; + +// ===== 原 scope.js ===== +// ============ scope.js ============ +// 作用域相关规则 + +// no-with +// 说明:with 语句在严格模式(ESM/use strict)下是语法错误,无法在普通 script 中安全演示, +// 故此处移除。该规则仍可通过 .eslintrc 的 parserOptions 演示,或使用 eval('with(...)') 场景。 + +// no-extend-native +function extendNative() { + String.prototype.padOut = function () {}; +} + +// no-eval +function evalDemo(input) { + return eval(input); +} + +// no-param-reassign +function paramReassign(x) { + x = x * 2; + return x; +} + +// no-unmodified-loop-condition +function unmodifiedLoop(i) { + while (i < 10) { + console.log(i); + } +} + +// no-unreachable-loop +function unreachableLoop() { + const items = [1, 2, 3]; + for (const item of items) { + break; + } + while (true) { + break; + } +} + +// no-unmodified-loop-condition already covered + +// no-unreachable-loop already covered + +// ===== 原 syntax.js ===== +// ============ syntax.js ============ +// 语法类规则:正则、转义、字面量语法问题 + +// no-invalid-regexp +const badRegex = new RegExp('['); + +// no-regex-spaces +const regexSpaces = /a b/; + +// no-control-regex +const controlRegex = /\x1f/; + +// no-empty-character-class +const emptyCharClass = /abc[]/; + +// no-misleading-character-class +const misleadingCharClass = /[👍👎]/g; + +// no-useless-backreference +const uselessBackref = /(\1a)/; + +// no-useless-escape +const uselessEscape = 'line\.'; + +// no-irregular-whitespace +const irregularWhitespace = 'dissimilar\u00A0space'; + +// no-loss-of-precision +const lossOfPrecision = 2.1234567890123456789012345678901234567890; + +// no-sparse-arrays +const sparseArray = [1, , 2]; + +// no-empty +function emptyFunctionBody() {} + +// no-empty (related with no-empty-function? no, plain block) +if (sparseArray.length > 0) { +} + +// no-constant-binary-expression +const constantBinary = 'a' && 'b'; + +// no-constant-condition +if (true) { + console.log('always true'); +} + +// no-cond-assign +let condVar; +if (condVar = 1) { + console.log('assignment in condition'); +} + +// no-self-assign +let selfAssigned = 5; +selfAssigned = selfAssigned; + +// no-debugger +debugger; + +// no-extra-boolean-cast +if (!!lossOfPrecision) { + console.log('double negation'); +} + +// no-ex-assign +try { + throw new Error('boom'); +} catch (e) { + e = new Error('reassigned'); +} + +// no-empty-pattern +function noEmptyPattern({}) { + return 1; +} + +// no-unreachable +function unreachableCode() { + return 1; + console.log('never reached'); +} + +// no-unexpected-multiline +const unexpectedMultiline = lossOfPrecision +(function () { + console.log('IIFE'); +})(); + +// no-unsafe-optional-chaining +function unsafeOptional(obj) { + return (obj?.a).b; +} + +// no-unsafe-negation +let flagFlag = true; +const holderObj = {}; +if (!flagFlag in holderObj) { + console.log('negated in'); +} + +// no-unsafe-finally +function unsafeFinally() { + try { + throw new Error('inner'); + } finally { + return 1; + } +} + +// no-unused-labels +unusedLabel: for (let i = 0; i < 3; i++) { + console.log(i); +} + +// no-unused-vars +const completelyUnusedVariable = 42; + +// no-useless-catch +function uselessCatch() { + try { + return 1; + } catch (err) { + throw err; + } +} + +// no-useless-escape already covered above + +// no-irregular-whitespace already covered + +// no-empty-character-class already covered + +// no-control-regex already covered + +// no-regex-spaces already covered + +// no-invalid-regexp already covered + +// no-nonoctal-decimal-escape already covered +// no-irregular-whitespace (actual NBSP character on this line) +const irregularWS = 1;  + +// ===== 原 variables.js ===== +// ============ variables.js ============ +// 变量声明类规则 + +// no-var +var oldStyleVar = 1; + +// no-undef +console.log(undefinedGlobalThing); + +// no-redeclare +function redeclareDemo() { + var redeclaredName = 1; + var redeclaredName = 2; +} + +// no-dupe-args +// 说明:function(a, a) 重复形参在严格模式是语法错误(Argument name clash), +// 已由 class DupeMember 的 no-dupe-class-members 覆盖"重复成员"演示,此处移除。 + +// no-delete-var +// 说明:严格模式下 delete 局部变量是语法错误(Deleting local variable in strict mode), +// 此处改为删除对象属性以保留 no-delete-var 规则的演示语义(delete 一元操作符)。 +function deleteVarDemo() { + const obj = { localVar: 1 }; + delete obj.localVar; +} + +// no-shadow-restricted-names +function shadowRestricted(undefined) { + return undefined; +} + +// no-global-assign +function globalAssign() { + Math = {}; +} + +// no-func-assign (in functions.js maybe) +// no-class-assign (in classes.js) + +// block-scoped-var +function blockScopedDemo() { + if (true) { + var blockScoped = 1; + } + console.log(blockScoped); +} + +// no-unassigned-vars +function unassignedVarDemo() { + let unassignedVariable; + return unassignedVariable; +} + +// no-shadow +function shadowDemo() { + let value = 1; + function inner() { + let value = 2; + return value; + } + return inner(); +} + +// no-useless-assignment +function uselessAssignment() { + let x = 1; + x = 2; + return x; +} + +// no-array-constructor +const arrayViaConstructor = new Array(1, 2, 3); + +// no-new-native-nonconstructor +const symbolViaNew = new Symbol('x'); + +// no-obj-calls +const jsonAsFunction = JSON(); + +// no-const-assign +const immutableValue = 1; +immutableValue = 2; + +// no-prototype-builtins +function protoBuilds(obj) { + return obj.hasOwnProperty('key'); +} + +// no-import-assign +// 说明:本文件为普通 script(无 import/export),no-import-assign 需要 ESM 模块绑定, +// 该规则由 typesmodule.ts(ESM 模块文件)演示,此处移除占位。 \ No newline at end of file diff --git a/data/demo-eslint/src/esm-demo.js b/data/demo-eslint/src/esm-demo.js new file mode 100644 index 0000000..caa83dc --- /dev/null +++ b/data/demo-eslint/src/esm-demo.js @@ -0,0 +1,11 @@ +// ============ esm-demo.js ============ +// ESM 模块文件:演示 no-import-assign(module 模式下对 import 绑定赋值) + +// no-import-assign +import fs from 'fs'; + +function reassignImport() { + fs = 1; +} + +console.log(fs); diff --git a/data/demo-eslint/src/legacysyntax.cjs b/data/demo-eslint/src/legacysyntax.cjs new file mode 100644 index 0000000..ef81b1b --- /dev/null +++ b/data/demo-eslint/src/legacysyntax.cjs @@ -0,0 +1,36 @@ +/* eslint preserve-caught-error: "error" */ +// ============ legacysyntax.cjs ============ +// .cjs 扩展名使 ESLint flat config 默认按 commonjs(宽松)模式解析本文件, +// 无需任何工程配置(eslint.config.mjs)即可触发仅 sloppy 模式合法的旧式语法规则。 +// 首行行内注释单独启用 preserve-caught-error(eslint 9.39.4 引擎已实现该规则,但内置配置未启用)。 + +// no-nonoctal-decimal-escape +const nonOctalEscape = '\8 and \9'; + +// 顺手在此以宽松模式再触发 no-octal(旧式八进制字面量) +const legacyOctal = 010; + +// ===== 补充:其余仅宽松模式合法的旧式语法样例 ===== + +// no-with +with ({ pi: 3.14, e: 2.72 }) { + console.log(pi, e); +} + +// no-delete-var +var deletable = 42; +delete deletable; + +// no-dupe-args +function duplicateArgs(a, a) { + return a; +} + +// preserve-caught-error(catch 中抛新错误应保留原始错误) +try { + JSON.parse('not json'); +} catch (err) { + throw new Error('parse failed'); +} + +console.log(nonOctalEscape, legacyOctal, duplicateArgs(1, 2)); diff --git a/data/demo-eslint/src/legacysyntax.js b/data/demo-eslint/src/legacysyntax.js new file mode 100644 index 0000000..5d6342b --- /dev/null +++ b/data/demo-eslint/src/legacysyntax.js @@ -0,0 +1,34 @@ +// ============ legacysyntax.js ============ +// 该文件以 script(非严格)模式解析,用于触发需要在 sloppy 模式下才能合法的旧式转义规则。 +// eslint.config.mjs 中为其单独设置了 sourceType: "script"。 + +// no-nonoctal-decimal-escape +const nonOctalEscape = '\8 and \9'; + +// 顺手在此以 script 模式再触发 no-octal(旧式八进制字面量) +const legacyOctal = 010; + +// ===== 补充:其余仅 sloppy 模式合法的旧式语法样例(原 common.js 中被移除的致命样例)===== + +// no-with +with ({ pi: 3.14, e: 2.72 }) { + console.log(pi, e); +} + +// no-delete-var +var deletable = 42; +delete deletable; + +// no-dupe-args +function duplicateArgs(a, a) { + return a; +} + +// preserve-caught-error(ESLint 10 新增,catch 中抛新错误应保留原始错误) +try { + JSON.parse('not json'); +} catch (err) { + throw new Error('parse failed'); +} + +console.log(nonOctalEscape, legacyOctal, duplicateArgs(1, 2)); \ No newline at end of file diff --git a/data/demo-eslint/src/typescript.ts b/data/demo-eslint/src/typescript.ts new file mode 100644 index 0000000..3c85cd0 --- /dev/null +++ b/data/demo-eslint/src/typescript.ts @@ -0,0 +1,213 @@ +// ============ typescript.ts ============ +// TypeScript 规则演示(覆盖 ts.configs.recommended + 插件精选规则) + +// triple-slash-reference: 禁止非类型导入的三斜线指令 +/// + +// consistent-type-imports +import { DemoType } from './typesmodule'; + +// no-require-imports / no-var +function legacyLoad() { + var mod = require('./typesmodule'); + return mod; +} + +// no-array-constructor +function arrayConstructor() { + return new Array(1, 2, 3); +} + +// no-dynamic-delete +function dynamicDelete(obj: Record, key: string) { + delete obj[key]; +} + +// no-extraneous-class +class UtilityClass { + static helper(): number { + return 42; + } +} + +// no-invalid-void-type +function invalidVoid(): void | undefined { + return undefined; +} + +// no-non-null-asserted-nullish-coalescing +function nullishAsserted(input: string | null) { + const value: string | null = input; + return value! ?? 'fallback'; +} + +// no-non-null-assertion 与 no-extra-non-null-assertion +function nonNullAssert(value: string | null) { + return value!!.length; +} + +// no-non-null-asserted-optional-chain +function optionalChainAsserted(obj: { a?: { b?: string } }) { + return obj?.a?.b!; +} + +// no-this-alias +function thisAlias(this: { x: number }) { + const self = this; + return self.x; +} + +// no-shadow 与 prefer-const +function shadowInTs() { + const shadowed = 1; + const inner = () => { + const shadowed = 2; + return shadowed; + }; + return inner(); +} + +// prefer-const(@typescript-eslint/prefer-const:重新赋值前不应使用 let) +function preferConstInTs() { + let fixed = 1; + return fixed; +} + +// no-useless-constructor +class EmptyConstructor { + constructor() {} +} + +// no-useless-empty-export +export {}; + +// prefer-enum-initializers 与 no-duplicate-enum-values +enum UninitializedEnum { + First, + Second, +} +enum DuplicateEnum { + A = 1, + B = 1, +} + +// prefer-literal-enum-member +const someValue = 10; +enum NonLiteralEnum { + A = someValue, +} + +// unified-signatures +function unifiedDemo(a: string): void; +function unifiedDemo(a: 'x'): void; +function unifiedDemo(a: string): void { + console.log(a); +} + +// no-explicit-any +function explicitAny(value: any): any { + return value; +} + +// no-empty-object-type +type EmptyObject = {}; +interface EmptyInterface {} +const emptyObj: EmptyObject = {}; + +// no-wrapper-object-types +function wrapperTypes(s: String, n: Number, b: Boolean) { + return s + n + b; +} + +// no-unsafe-function-type +type UnsafeFn = Function; +function unsafeFn(fn: UnsafeFn) { + fn(); +} + +// no-unused-expressions +function unusedExpr(flag: boolean, value: number) { + flag && value; + value; +} + +// no-unnecessary-type-constraint +function pointlessConstraint(t: T): T { + return t; +} + +// no-unsafe-declaration-merging +interface MergedThing { + method(): void; +} +class MergedThing { + value = 1; +} + +// ban-ts-comment +function tsIgnored() { + // @ts-ignore + const x = 1; + return x; +} + +// no-misused-new +interface MisusedNew { + new (): MisusedNew; + constructor(): void; +} + +// no-namespace 与 prefer-namespace-keyword +namespace LegacyNamespace { + export const value = 1; +} +module LegacyModule { + export const value = 2; +} + +// prefer-as-const +function preferAsConst() { + const literal: 'hello' = 'hello'; + return literal; +} + +// prefer-rest-params 与 prefer-spread +function restParams() { + const args = arguments; + return Array.prototype.slice.call(args); +} +declare function mySpreadFn(...args: number[]): number; +function spreadArgs(base: number[]) { + return mySpreadFn.apply(undefined, base); +} + +// preserve-caught-error(ESLint 10 新增,catch 中抛新错误应保留原始错误) +function swallowError(input: string): number { + try { + return JSON.parse(input) as number; + } catch (err) { + throw new Error('parse failed'); + } +} + +const demoValue: DemoType = 'demo'; +void demoValue; +void swallowError; + +export { + dynamicDelete, + nullishAsserted, + nonNullAssert, + shadowInTs, + unifiedDemo, + explicitAny, + wrapperTypes, + unsafeFn, + unusedExpr, + pointlessConstraint, + preferAsConst, + restParams, + spreadArgs, + LegacyNamespace, + LegacyModule, +}; \ No newline at end of file diff --git a/data/demo-eslint/src/typesmodule.ts b/data/demo-eslint/src/typesmodule.ts new file mode 100644 index 0000000..48467d8 --- /dev/null +++ b/data/demo-eslint/src/typesmodule.ts @@ -0,0 +1,8 @@ +// ============ typesmodule.ts ============ +// 供 typescript.ts 使用的类型模块 + +export interface stringifyType { + (input: string): string; +} + +export type DemoType = string | number; \ No newline at end of file diff --git a/data/demo-sqlfluff/coverage_map.json b/data/demo-sqlfluff/coverage_map.json new file mode 100644 index 0000000..92601e9 --- /dev/null +++ b/data/demo-sqlfluff/coverage_map.json @@ -0,0 +1,290 @@ +{ + "enabled": [ + "AL02", + "AL03", + "AL04", + "AL05", + "AL06", + "AL08", + "AL09", + "AL10", + "AM01", + "AM02", + "AM03", + "AM05", + "AM06", + "AM08", + "CP01", + "CP02", + "CP03", + "CP04", + "CP05", + "CV01", + "CV02", + "CV03", + "CV04", + "CV05", + "CV06", + "CV08", + "CV12", + "JJ01", + "LT01", + "LT02", + "LT05", + "LT06", + "LT07", + "LT08", + "LT10", + "LT11", + "LT12", + "LT13", + "LT14", + "LT15", + "RF01", + "RF02", + "RF04", + "RF05", + "RF06", + "ST01", + "ST02", + "ST03", + "ST04", + "ST05", + "ST06", + "ST07", + "ST08", + "ST09", + "ST10", + "ST11", + "ST12" + ], + "mapping": { + "AL02": [ + "01_aliasing.sql:5" + ], + "AL03": [ + "03_caps.sql:21", + "03_caps.sql:22", + "03_caps.sql:36", + "03_caps.sql:37", + "07_structure.sql:50" + ], + "AL04": [ + "01_aliasing.sql:21" + ], + "AL05": [ + "01_aliasing.sql:27", + "02_ambiguity.sql:60" + ], + "AL08": [ + "01_aliasing.sql:32", + "07_structure.sql:42" + ], + "AL09": [ + "01_aliasing.sql:38" + ], + "AL10": [ + "01_aliasing.sql:46" + ], + "AM01": [ + "02_ambiguity.sql:2" + ], + "AM02": [ + "02_ambiguity.sql:16", + "05_layout.sql:41" + ], + "AM03": [ + "02_ambiguity.sql:28" + ], + "AM05": [ + "01_aliasing.sql:20", + "02_ambiguity.sql:38", + "02_ambiguity.sql:59", + "04_convention.sql:50", + "06_references.sql:12", + "07_structure.sql:45", + "07_structure.sql:62", + "07_structure.sql:77" + ], + "AM06": [ + "02_ambiguity.sql:50" + ], + "AM08": [ + "02_ambiguity.sql:59" + ], + "CP01": [ + "03_caps.sql:11", + "03_caps.sql:14", + "03_caps.sql:16", + "03_caps.sql:20", + "03_caps.sql:23", + "03_caps.sql:27", + "03_caps.sql:29", + "03_caps.sql:31", + "03_caps.sql:32", + "03_caps.sql:32", + "03_caps.sql:32", + "03_caps.sql:35", + "03_caps.sql:36", + "03_caps.sql:37", + "03_caps.sql:38" + ], + "CP02": [ + "03_caps.sql:12", + "03_caps.sql:17" + ], + "CP03": [ + "03_caps.sql:22" + ], + "CP04": [ + "03_caps.sql:32" + ], + "CP05": [ + "03_caps.sql:37" + ], + "CV01": [ + "04_convention.sql:7" + ], + "CV02": [ + "04_convention.sql:11" + ], + "CV03": [ + "04_convention.sql:18" + ], + "CV04": [ + "04_convention.sql:24" + ], + "CV05": [ + "04_convention.sql:34" + ], + "CV06": [ + "04_convention.sql:61" + ], + "CV08": [ + "04_convention.sql:41" + ], + "CV12": [ + "04_convention.sql:50", + "04_convention.sql:52" + ], + "JJ01": [ + "08_jinja.sql:2" + ], + "LT01": [ + "05_layout.sql:2", + "05_layout.sql:19", + "07_structure.sql:67" + ], + "LT02": [ + "01_aliasing.sql:21", + "02_ambiguity.sql:39", + "04_convention.sql:42", + "05_layout.sql:5", + "05_layout.sql:25", + "06_references.sql:13", + "07_structure.sql:46", + "07_structure.sql:63", + "07_structure.sql:67", + "07_structure.sql:78", + "07_structure.sql:94" + ], + "LT05": [ + "05_layout.sql:13", + "07_structure.sql:1" + ], + "LT06": [ + "05_layout.sql:19" + ], + "LT07": [ + "05_layout.sql:25" + ], + "LT08": [ + "05_layout.sql:26", + "05_layout.sql:32", + "07_structure.sql:23" + ], + "LT10": [ + "07_structure.sql:66" + ], + "LT11": [ + "05_layout.sql:41" + ], + "LT12": [ + "01_aliasing.sql:47", + "02_ambiguity.sql:61", + "03_caps.sql:40", + "04_convention.sql:63", + "05_layout.sql:42", + "05b_layout_misc.sql:10", + "05c_file_boundaries.sql:4", + "06_references.sql:32", + "07_structure.sql:101", + "08_jinja.sql:6" + ], + "LT13": [ + "05c_file_boundaries.sql:1" + ], + "LT14": [ + "05b_layout_misc.sql:2" + ], + "LT15": [ + "05b_layout_misc.sql:8" + ], + "RF01": [ + "06_references.sql:3" + ], + "RF02": [ + "06_references.sql:9" + ], + "RF04": [ + "06_references.sql:19", + "07_structure.sql:35" + ], + "RF05": [ + "06_references.sql:23" + ], + "RF06": [ + "06_references.sql:29" + ], + "ST01": [ + "07_structure.sql:3" + ], + "ST02": [ + "07_structure.sql:13" + ], + "ST03": [ + "07_structure.sql:20" + ], + "ST04": [ + "07_structure.sql:32" + ], + "ST05": [ + "07_structure.sql:46" + ], + "ST06": [ + "07_structure.sql:49" + ], + "ST07": [ + "07_structure.sql:63" + ], + "ST08": [ + "07_structure.sql:67" + ], + "ST09": [ + "07_structure.sql:78" + ], + "ST10": [ + "07_structure.sql:86" + ], + "ST11": [ + "04_convention.sql:42", + "07_structure.sql:94" + ], + "ST12": [ + "07_structure.sql:100" + ], + "AL06": [ + "09_aliasing_length.sql:6" + ] + } +} \ No newline at end of file diff --git a/data/demo-sqlfluff/reports/01_aliasing-review.md b/data/demo-sqlfluff/reports/01_aliasing-review.md new file mode 100644 index 0000000..f73a187 --- /dev/null +++ b/data/demo-sqlfluff/reports/01_aliasing-review.md @@ -0,0 +1,55 @@ +# 代码审查报告 + +**文件:** `data\demo-sqlfluff\src\01_aliasing.sql` +**语言:** sql +**耗时:** 102.5s +**分析工具:** sqlfluff + +--- + +总计: 12 | 错误: 10 | 警告: 1 | 建议: 1 + +静态分析 · 9 个问题 + +- 🔴 `sqlfluff:AL02` L5 + 第 5 行:列别名使用隐式/显式混用的别名形式。 + 建议: 请为列别名显式添加 AS 关键字,例如将 `col1 col1_alias` 改为 `col1 AS col1_alias`。 +- 🔴 `sqlfluff:AM05` L20 + 第 20 行:JOIN 子句应使用完全限定的列引用。 + 建议: 为每个表使用唯一别名,并确保 ON 条件中的列都以该表别名限定;例如 `ON t1.id = t2.ref_id`,而不是 `other_table.ref_id`。 +- 🔴 `sqlfluff:AL04` L21 + 第 21 行:表别名 't1' 重复使用。表别名应保持唯一。 + 建议: 将第二张表 other_table 的别名改为唯一别名(如 t2),并同步修改 ON 条件中的引用为 `t1.id = t2.ref_id`。 +- 🔴 `sqlfluff:LT02` L21 + 第 21 行:'ON' 前应换行且不应有缩进。 + 建议: 将 ON 放到新的行,并不加额外缩进:`other_table AS t1\nON t1.id = other_table.ref_id;` +- 🔴 `sqlfluff:AL05` L27 + 第 27 行:表别名 'unused_alias' 在 SELECT 语句中从未使用。 + 建议: 移除未使用的表别名,直接使用表名;或若该别名有意义,在查询中引用它。 +- 🔴 `sqlfluff:AL08` L32 + 第 32 行:列别名 'shared_alias' 与第 31 行的别名重复。 + 建议: 为这两列设置不同的别名,例如将第 32 行的 `name AS shared_alias` 改为 `name AS name_alias`。 +- 🔴 `sqlfluff:AL09` L38 + 第 38 行:列不应使用与列名相同的别名(自别名)。 + 建议: 移除 `AS id`,直接写 `id`;或改成一个有意义的别名如 `id_alias`。 +- 🔴 `sqlfluff:AL10` L46 + 第 46 行:派生表必须指定别名。 + 建议: 为子查询添加表别名,例如 `(SELECT COUNT(*) AS cnt FROM my_table) AS cnt_table`。 +- 🔴 `sqlfluff:LT12` L47 + 第 47 行:文件必须以单个尾随换行符结束。 + 建议: 在文件末尾添加一个换行符,并确保没有多余的空行或行尾空格。 + +AI 审查 · 3 条建议 + +- 🔴 [AI] [bug] `invalid-alias-original-table-reference` L21 + **ON 条件中混用原始表名与别名** + 第 21 行中,`other_table AS t1` 为表设置了别名,但随后 ON 子句使用 `other_table.ref_id` 引用该表。大多数 SQL 方言中,一旦表指定别名,就必须用别名限定列,不能再用原始表名;且该查询还存在重复别名 t1,导致 `t1.id` 指代不明。 + 建议: 为 other_table 使用唯一别名(如 t2),并将 ON 条件改为 `t1.id = t2.ref_id`,同时保持两个表别名唯一。 +- 🟡 [AI] [performance] `redundant-derived-table` L46 + **不必要的外层查询嵌套** + 第 43-46 行 `SELECT cnt FROM (SELECT COUNT(*) AS cnt FROM my_table)` 先用子查询计算 COUNT(*),再由外层选择 cnt;这层封装没有实际作用,反而让 SQL 更难阅读且可能妨碍优化器简化执行计划。 + 建议: 直接写成 `SELECT COUNT(*) AS cnt FROM my_table;`,既避免派生表别名问题,也减少不必要的嵌套。 +- 🔵 [AI] [style] `missing-column-alias` L11 + **聚合列缺少别名** + 第 11 行 `SUM(amount)` 没有显式别名,返回列名会依赖具体数据库的默认命名规则,不利于后续代码维护与结果集访问。 + 建议: 为聚合表达式添加有意义且稳定的别名,例如 `SUM(amount) AS total_amount`。 diff --git a/data/demo-sqlfluff/reports/02_ambiguity-review.md b/data/demo-sqlfluff/reports/02_ambiguity-review.md new file mode 100644 index 0000000..f1b4ef7 --- /dev/null +++ b/data/demo-sqlfluff/reports/02_ambiguity-review.md @@ -0,0 +1,60 @@ +# 代码审查报告 + +**文件:** `data\demo-sqlfluff\src\02_ambiguity.sql` +**语言:** sql +**耗时:** 182.4s +**分析工具:** sqlfluff + +--- + +总计: 13 | 错误: 10 | 警告: 2 | 建议: 1 + +静态分析 · 10 个问题 + +- 🔴 `sqlfluff:AM01` L6 + 第 6 行:在带有 GROUP BY 的 SELECT 语句中使用 DISTINCT 含义模糊。 + 建议: 去掉 DISTINCT 或 GROUP BY 中的一个。若只需按 dept、region 去重,可保留 GROUP BY 并去掉 DISTINCT;若不需要分组聚合,则保留 DISTINCT 并去掉 GROUP BY。 +- 🔴 `sqlfluff:AM02` L20 + 第 20 行:建议使用 UNION DISTINCT 或 UNION ALL,而不是裸 UNION。 + 建议: 根据语义选择:若希望保留所有重复行,把 UNION 改为 UNION ALL;若希望去重,改为 UNION DISTINCT(需方言支持,如 ansi/bigquery/mysql)。 +- 🔴 `sqlfluff:AM03` L32 + 第 32 行:ORDER BY 子句含义模糊;ORDER BY 应对所有列都指定排序方向,或所有列都不指定。 + 建议: 若只想让 a 升序、b 降序,显式写为 ORDER BY a ASC, b DESC;如果想让两列都降序,则写为 ORDER BY a DESC, b DESC。 +- 🔴 `sqlfluff:AM05` L42 + 第 42 行:JOIN 子句应使用完整限定形式。 + 建议: 将 JOIN 改为 INNER JOIN(或根据语义使用 LEFT/RIGHT/FULL JOIN),明确连接类型。 +- 🔴 `sqlfluff:LT02` L43 + 第 43 行:'ON' 前应有换行,且 'ON' 前不要有缩进。 + 建议: 把 ON 子句放到新行,并确保 ON 前没有额外缩进,例如:JOIN + other_table AS b +ON a.id = b.id; +- 🔴 `sqlfluff:AM06` L54 + 第 54 行:GROUP BY 与 ORDER BY 中的列引用方式不一致。 + 建议: 统一引用方式:要么都使用列名(GROUP BY a, b 和 ORDER BY a, b),要么都使用序号(GROUP BY 1, 2 和 ORDER BY 1, 2);推荐使用列名,避免 SELECT 列表变化时失效。 +- 🔴 `sqlfluff:AM05` L63 + 第 63 行:JOIN 子句应使用完整限定形式。 + 建议: 若确实要交叉连接,显式写 CROSS JOIN;若本意是内连接,请补充 ON 条件并写 INNER JOIN。 +- 🔴 `sqlfluff:AM08` L63 + 第 63 行:检测到隐式交叉连接(JOIN 缺少 ON 条件)。 + 建议: 如果意图是笛卡尔积,请显式写 CROSS JOIN;如果本意是按某列关联,请补上 ON 连接条件。 +- 🔴 `sqlfluff:AL05` L64 + 第 64 行:别名 'b' 在 SELECT 语句中从未被使用。 + 建议: 移除未使用的别名 b;若后续需要引用 other_table 的列,请使用 b.列名。 +- 🔴 `sqlfluff:LT12` L65 + 第 65 行:文件必须以单个尾随换行符结束。 + 建议: 在文件末尾保留且仅保留一个换行符,不要有多余空行或缺少换行。 + +AI 审查 · 3 条建议 + +- 🟡 [AI] [design] `dialect-dependent-lint-config` L2 + **AM02 规则受方言门控,默认配置下会漏报** + 顶部注释说明 AM02 仅对 ansi/bigquery/mysql 等支持 UNION DISTINCT 的方言生效,默认方言 oracle 不在白名单。如果 CI 使用默认 SQLFluff 配置,即使启用所有规则也不会报告第 20 行的裸 UNION,可能造成本地与 CI 的检查结果不一致。 + 建议: 在项目 .sqlfluff 中显式设置目标方言(例如 ansi),并确保本地与 CI 使用同一配置;如果目标方言确为 oracle,应关闭或忽略 AM02。 +- 🟡 [AI] [bug] `unnecessary-cartesian-join` L63 + **最终查询的交叉连接可能完全是多余连接,产生重复和错误结果** + 第 59-64 行 SELECT a.id 只引用了 my_table 的别名 a,却对 other_table 做无 ON 条件的 JOIN(隐式交叉连接)。只要 other_table 非空,每个 a.id 会被重复 other_table 行数次;若 other_table 为空,查询反而返回空集。这通常不是预期语义,也会带来不必要的笛卡尔积开销。 + 建议: 如果只想返回 my_table 的所有 id,直接删除 JOIN other_table;如果只想返回在 other_table 中有匹配的 id,应使用 INNER JOIN ... ON 并配合 SELECT DISTINCT 或 EXISTS,避免重复。 +- 🔵 [AI] [design] `stale-rule-comments` L14 + **修复后示例注释将与 SQL 语义不一致** + 代码中的 -- AM01、-- AM02 等注释标记的是静态分析规则样例。一旦按上述建议应用修复(例如删除 DISTINCT、把 UNION 改为 UNION ALL、把 JOIN 改为 INNER JOIN/CROSS JOIN),这些注释就不再准确,会误导后续阅读者。 + 建议: 在应用修复后同步更新或删除对应注释;或将原始违规样例与修复后样例分开存放,并注明预期触发的规则。 diff --git a/data/demo-sqlfluff/reports/03_caps-review.md b/data/demo-sqlfluff/reports/03_caps-review.md new file mode 100644 index 0000000..8fb0dc8 --- /dev/null +++ b/data/demo-sqlfluff/reports/03_caps-review.md @@ -0,0 +1,107 @@ +# 代码审查报告 + +**文件:** `data\demo-sqlfluff\src\03_caps.sql` +**语言:** sql +**耗时:** 190.8s +**分析工具:** sqlfluff + +--- + +总计: 29 | 错误: 25 | 警告: 2 | 建议: 2 + +静态分析 · 25 个问题 + +- 🔴 `sqlfluff:CP01` L11 + 第11行:关键字必须一律小写。 + 建议: 将 SELECT 改为小写 select。 +- 🔴 `sqlfluff:CP02` L12 + 第12行:未加引号的标识符必须一律小写。 + 建议: 将 colA 改为小写 cola,并确保其他位置的同一标识符也统一小写。 +- 🔴 `sqlfluff:CP01` L14 + 第14行:关键字必须一律小写。 + 建议: 将 FROM 改为小写 from。 +- 🔴 `sqlfluff:CP01` L16 + 第16行:关键字必须一律小写。 + 建议: 将 WHERE 改为小写 where。 +- 🔴 `sqlfluff:CP02` L17 + 第17行:未加引号的标识符必须一律小写。 + 建议: 将 coLa 改为小写 cola,与第12行的 colA 统一。 +- 🔴 `sqlfluff:CP01` L20 + 第20行:关键字必须一律小写。 + 建议: 将 SELECT 改为小写 select。 +- 🔴 `sqlfluff:AL03` L21 + 第21行:列表达式缺少别名。请使用显式 AS 子句。 + 建议: 为 SUM(amount) 添加别名,例如:SUM(amount) AS total_amount。 +- 🔴 `sqlfluff:AL03` L22 + 第22行:列表达式缺少别名。请使用显式 AS 子句。 + 建议: 为 sum(quantity) 添加别名,例如:sum(quantity) AS total_quantity。 +- 🔴 `sqlfluff:CP03` L22 + 第22行:函数名必须一律大写。 + 建议: 将 sum 改为 SUM,与第21行的 SUM 保持一致。 +- 🔴 `sqlfluff:CP01` L23 + 第23行:关键字必须一律小写。 + 建议: 将 FROM 改为小写 from。 +- 🔴 `sqlfluff:CP01` L27 + 第27行:关键字必须一律小写。 + 建议: 将 SELECT 改为小写 select。 +- 🔴 `sqlfluff:CP01` L29 + 第29行:关键字必须一律小写。 + 建议: 将 FROM 改为小写 from。 +- 🔴 `sqlfluff:CP01` L31 + 第31行:关键字必须一律小写。 + 建议: 将 WHERE 改为小写 where。 +- 🔴 `sqlfluff:CP01` L32 + 第32行:关键字必须一律小写。 + 建议: 将 flag IS NULL 中的 IS 改为 is。 +- 🔴 `sqlfluff:CP01` L32 + 第32行:关键字必须一律小写。 + 建议: 将 OR 改为 or。 +- 🔴 `sqlfluff:CP01` L32 + 第32行:关键字必须一律小写。 + 建议: 将 other_flag IS null 中的 IS 改为 is。 +- 🔴 `sqlfluff:CP04` L32 + 第32行:布尔/null 字面量必须一律大写。 + 建议: 将小写 null 改为 NULL。 +- 🔴 `sqlfluff:CP01` L35 + 第35行:关键字必须一律小写。 + 建议: 将 SELECT 改为小写 select。 +- 🔴 `sqlfluff:AL03` L36 + 第36行:列表达式缺少别名。请使用显式 AS 子句。 + 建议: 为 CAST(id AS VARCHAR) 添加别名,例如:CAST(id AS VARCHAR) AS id_string。 +- 🔴 `sqlfluff:CP01` L36 + 第36行:关键字必须一律小写。 + 建议: 将 AS 改为小写 as。 +- 🔴 `sqlfluff:AL03` L37 + 第37行:列表达式缺少别名。请使用显式 AS 子句。 + 建议: 为 CAST(amount AS varchar) 添加别名,例如:CAST(amount AS varchar) AS amount_string。 +- 🔴 `sqlfluff:CP01` L37 + 第37行:关键字必须一律小写。 + 建议: 将 AS 改为小写 as。 +- 🔴 `sqlfluff:CP05` L37 + 第37行:数据类型必须一律大写。 + 建议: 将 varchar 改为 VARCHAR,与第36行的 VARCHAR 保持一致。 +- 🔴 `sqlfluff:CP01` L38 + 第38行:关键字必须一律小写。 + 建议: 将 FROM 改为小写 from。 +- 🔴 `sqlfluff:LT12` L40 + 第40行:文件必须以单个尾部换行结束。 + 建议: 在文件末尾添加一个换行符,并确保没有多余的空行。 + +AI 审查 · 4 条建议 + +- 🟡 [AI] [bug] `or-condition-maybe-and` L32 + **OR 条件可能将意图的 AND 写成 OR,导致结果范围扩大** + 第32行 `flag IS NULL OR other_flag IS null` 会返回只要任一标志为 NULL 的行;如果业务意图是查找两个标志都为 NULL 的行,应使用 AND。请确认实际语义。 + 建议: 若意图是同时为 NULL,请将 OR 改为 AND。 +- 🟡 [AI] [bug] `cast-without-length` L36 + **CAST 未指定目标长度,可能导致数据截断** + 第36行和第37行的 CAST 将列转换为 VARCHAR 时未指定长度。不同数据库对无长度 VARCHAR 的默认行为不同(如 SQL Server 默认 30,Oracle 要求必须指定),若 id/amount 超出默认长度将产生静默截断。 + 建议: 为 CAST 指定明确的长度,例如 CAST(id AS VARCHAR(20))、CAST(amount AS VARCHAR(20)),或根据业务使用更合适的类型。 +- 🔵 [AI] [bug] `sum-may-return-null` L21 + **聚合函数 SUM 在无行时返回 NULL,可能影响下游计算** + 第21、22行的 SUM(amount) 和 sum(quantity) 如果没有匹配行,将返回 NULL 而非 0。如果业务逻辑期望数值 0,应使用 COALESCE 或 IFNULL 包裹。 + 建议: 使用 COALESCE(SUM(amount), 0) 和 COALESCE(SUM(quantity), 0) 以避免空值。 +- 🔵 [AI] [performance] `or-condition-index-performance` L32 + **WHERE 中使用 OR 可能阻止索引使用** + 第32行对两个可空字段使用 OR 条件,这类谓词通常难以利用索引,在数据量大时容易导致全表扫描。 + 建议: 若两个条件的选择性都较低,可考虑改写为 UNION ALL 以分别利用索引(注意重复行问题),或在应用层拆分查询。如果数据量不大,保持 OR 也可接受。 diff --git a/data/demo-sqlfluff/reports/04_convention-review.md b/data/demo-sqlfluff/reports/04_convention-review.md new file mode 100644 index 0000000..de14814 --- /dev/null +++ b/data/demo-sqlfluff/reports/04_convention-review.md @@ -0,0 +1,63 @@ +# 代码审查报告 + +**文件:** `data\demo-sqlfluff\src\04_convention.sql` +**语言:** sql +**耗时:** 202.1s +**分析工具:** sqlfluff + +--- + +总计: 15 | 错误: 14 | 警告: 1 | 建议: 0 + +静态分析 · 13 个问题 + +- 🔴 `sqlfluff:CV01` L7 + L7:`!=` 与 `<>` 混用,“不等于”运算符应保持一致。 + 建议: 将第 7 行中的 `<>` 改为 `!=`,使同一语句统一使用一种“不等于”写法。 +- 🔴 `sqlfluff:CV02` L11 + L11:应使用 `COALESCE` 而不是 `IFNULL`。 + 建议: 将 `IFNULL(amount, 0)` 替换为 `COALESCE(amount, 0)`,以保持 SQL 标准兼容。 +- 🔴 `sqlfluff:CV03` L18 + L18:SELECT 列表中不允许出现尾随逗号。 + 建议: 删除 `name` 后面的逗号,使其直接换行到 `FROM`。 +- 🔴 `sqlfluff:CV04` L24 + L24:统计行数应使用统一的 `COUNT(*)` 语法。 + 建议: 将 `COUNT(1)` 改为 `COUNT(*)`。 +- 🔴 `sqlfluff:CV05` L34 + L34:与 NULL 比较应使用 `IS NULL` / `IS NOT NULL`,不能使用 `= NULL`。 + 建议: 将 `end_date = NULL` 改为 `end_date IS NULL`;若想查非空则改为 `end_date IS NOT NULL`。 +- 🔴 `sqlfluff:CV08` L41 + L41:应使用 `LEFT JOIN` 而不是 `RIGHT JOIN`。 + 建议: 将 `RIGHT JOIN` 改写为 `LEFT JOIN`;若需保持原语义,应调换表顺序并改写为 `other_table AS b LEFT JOIN my_table AS a ON a.id = b.ref_id`。 +- 🔴 `sqlfluff:ST11` L42 + L42:连接表 `other_table AS b` 在查询的其他位置未被引用。 + 建议: 如果 `other_table AS b` 只是用于过滤,建议改为 `WHERE EXISTS (SELECT 1 FROM other_table AS b WHERE b.ref_id = a.id)`;若确实需要 b 的数据,应把 b 的字段加入 SELECT,否则移除该 JOIN。 +- 🔴 `sqlfluff:LT02` L42 + L42:`ON` 前应有换行,且 `ON` 不应缩进。 + 建议: 在 `ON` 前插入换行,并让 `ON` 从行首开始,不要缩进。 +- 🔴 `sqlfluff:AM05` L50 + L50:JOIN 子句应使用完整的连接条件(ON),不能只依赖 WHERE。 + 建议: 改为 `JOIN other_table AS b ON a.id = b.ref_id`,并从 WHERE 中移除该条件,避免产生隐式交叉连接。 +- 🔴 `sqlfluff:CV12` L50 + L50:连接条件应使用 `JOIN ... ON ...`,不要使用 `WHERE`。 + 建议: 在 `JOIN` 后直接写 `ON a.id = b.ref_id`。 +- 🔴 `sqlfluff:CV12` L52 + L52:连接条件已写在 WHERE 中,应移入 JOIN 的 ON 子句。 + 建议: 删除 WHERE,并把 `a.id = b.ref_id` 作为 JOIN 的 ON 条件。 +- 🔴 `sqlfluff:CV06` L61 + L61:语句必须以分号结束;分号应紧跟在语句最后,而不是单独成行。 + 建议: 删除第 62 行单独的分号,并将第 61 行末尾补上分号:`status_code = 'active';`。 +- 🔴 `sqlfluff:LT12` L63 + L63:文件必须以一个单独的尾随换行符结尾。 + 建议: 在文件末尾添加一个换行符,并确保不要出现多个空行。 + +AI 审查 · 2 条建议 + +- 🔴 [AI] [bug] `boolean-or-instead-of-and` L7 + **OR 条件导致状态过滤几乎恒真** + `status_code != 'active' OR status_code != 'pending'` 对所有非 NULL 的 status_code 都为真:当 status_code='active' 时第二个条件为真,当 status_code='pending' 时第一个条件为真。因此实际会返回除 NULL 外的所有行,与预期的“排除 active/pending”很可能相反。 + 建议: 改为 `status_code != 'active' AND status_code != 'pending'` 或 `status_code NOT IN ('active', 'pending')`。若需把 NULL 也包含,请使用 `COALESCE(status_code, '') NOT IN ('active', 'pending')`。 +- 🟡 [AI] [design] `right-join-unused-left-columns` L41 + **RIGHT JOIN 仅选择左表字段会产生 NULL 并可能产生重复行** + `my_table AS a RIGHT JOIN other_table AS b` 会保留所有 other_table 行;未被匹配时 a.id 为 NULL,且若 other_table 中 ref_id 重复会导致 a.id 重复。当前 SELECT 只输出 a.id,未输出 b 的任何列,这个 RIGHT JOIN 的语义很可能不是期望的。 + 建议: 若目标是保留 my_table 并以 other_table 过滤/补列,应改为 `my_table AS a LEFT JOIN other_table AS b`;若目标确实是保留 other_table 全量行,应把 other_table 放在 FROM 左侧并 `LEFT JOIN my_table`,同时检查是否需要输出 b 的列。 diff --git a/data/demo-sqlfluff/reports/05_layout-review.md b/data/demo-sqlfluff/reports/05_layout-review.md new file mode 100644 index 0000000..12979fb --- /dev/null +++ b/data/demo-sqlfluff/reports/05_layout-review.md @@ -0,0 +1,76 @@ +# 代码审查报告 + +**文件:** `data\demo-sqlfluff\src\05_layout.sql` +**语言:** sql +**耗时:** 176.0s +**分析工具:** sqlfluff + +--- + +总计: 16 | 错误: 12 | 警告: 3 | 建议: 1 + +静态分析 · 12 个问题 + +- 🔴 `sqlfluff:LT01` L2 + 第2行:'*' 前应只有单个空格,实际有 2 个空格。 + 建议: 删除 `SELECT` 与 `*` 之间的一个空格,改为 `SELECT * FROM my_table;`。 +- 🔴 `sqlfluff:LT02` L5 + 第5行:该行不应有缩进。 + 建议: 删除第5行行首的 4 个空格,使 `SELECT` 顶格书写。 +- 🔴 `sqlfluff:LT05` L13 + 第13行:行太长(95 个字符超过 80 上限)。 + 建议: 建议缩短该列名,或在 sqlfluff 配置中调整 `max_line_length`;如果这是演示代码,也可以在该行使用 `-- noqa: LT05` 忽略此规则。 +- 🔴 `sqlfluff:LT01` L19 + 第19行:左括号 '(' 前存在多余空白。 + 建议: 删除 `SUM` 与 `(` 之间的空格,改为 `SUM(amount)`。 +- 🔴 `sqlfluff:LT06` L19 + 第19行:函数名 `SUM` 后面未紧接左括号。 + 建议: 将 `SUM (amount)` 改为 `SUM(amount)`,函数名与左括号之间不要留空格。 +- 🔴 `sqlfluff:LT02` L25 + 第25行:')' 前应换行且不应有缩进。 + 建议: 把 `)` 移到下一行并顶格,即写成: +` SELECT id FROM my_table +)`。 +- 🔴 `sqlfluff:LT07` L25 + 第25行:WITH 子句的右括号应单独另起一行。 + 建议: 将 WITH 子句的 `)` 放到 `SELECT id FROM my_table` 之后的独立行,且行首不要缩进。 +- 🔴 `sqlfluff:LT08` L26 + 第26行:CTE 闭括号之后缺少空行。 + 建议: 在 `)` 与 `SELECT * FROM cte;` 之间插入一个空行。 +- 🔴 `sqlfluff:LT08` L32 + 第32行:CTE 闭括号之后缺少空行。 + 建议: 在 `)` 与 `SELECT * FROM cte;` 之间插入一个空行。 +- 🔴 `sqlfluff:AM02` L41 + 第41行:建议使用 `UNION DISTINCT` 或 `UNION ALL`,不要单独使用 `UNION`。 + 建议: 若需要去重,改用 `UNION DISTINCT`;若不需要去重,改用 `UNION ALL`(通常性能更好)。 +- 🔴 `sqlfluff:LT11` L41 + 第41行:集合运算符前后都应加换行。 + 建议: 将 `UNION` 放到单独一行,并让它前面和后面各有一行,例如: +`SELECT id FROM my_table +UNION +SELECT id FROM other_table;` +- 🔴 `sqlfluff:LT12` L42 + 文件末尾:文件必须以一个单独的换行符结束。 + 建议: 在最后一行之后补一个换行符,并确保末尾没有多余空行。 + +AI 审查 · 4 条建议 + +- 🟡 [AI] [design] `long-identifier` L13 + **过长的标识符影响可移植性与可维护性** + 第13行的列名本身接近80个字符,即使不考虑第13行的行长度限制,这个标识符也过长,可能超过部分数据库的标识符上限(如 MySQL 64、PostgreSQL 63、Oracle 128),并显著影响可读性与可维护性。 + 建议: 在建表/查询中改用简短、有意义的列名(如 `long_total`),并在应用层建立字段映射;若不能改名,请确认目标数据库的标识符上限并添加注释。 +- 🟡 [AI] [performance] `redundant-distinct` L35 + **DISTINCT 可能冗余** + 第35行 `SELECT DISTINCT id FROM my_table;` 如果 `id` 是主键或唯一列,`DISTINCT` 不会改变结果,却会引入额外的排序/哈希操作,增加查询开销。请确认 `id` 的唯一性;若已唯一,应去掉 `DISTINCT`。 + 建议: 若 `id` 已唯一,将 `SELECT DISTINCT id` 改为 `SELECT id`。 +- 🟡 [AI] [bug] `union-compatible-types` L41 + **UNION 两侧字段类型需保证兼容** + 第41行通过 `UNION` 合并 `my_table.id` 与 `other_table.id`。若两侧类型不同,数据库将进行隐式转换,可能造成转换错误、精度损失或性能下降。建议显式转换为同一类型后再合并。 + 建议: 根据实际类型显式转换,例如: +`SELECT CAST(id AS INTEGER) FROM my_table` +`UNION` +`SELECT CAST(id AS INTEGER) FROM other_table;` +- 🔵 [AI] [design] `avoid-select-star` L2 + **避免使用 SELECT *** + 第2、26、32行使用了 `SELECT *`。如果表结构日后新增列,结果集会随之变化,可能意外暴露敏感字段并增加不必要的数据传输。建议在正式查询中显式列出所需字段。 + 建议: 将 `SELECT *` 改为显式字段列表,例如只需要 `id` 时写 `SELECT id`。 diff --git a/data/demo-sqlfluff/reports/05b_layout_misc-review.md b/data/demo-sqlfluff/reports/05b_layout_misc-review.md new file mode 100644 index 0000000..68ab972 --- /dev/null +++ b/data/demo-sqlfluff/reports/05b_layout_misc-review.md @@ -0,0 +1,34 @@ +# 代码审查报告 + +**文件:** `data\demo-sqlfluff\src\05b_layout_misc.sql` +**语言:** sql +**耗时:** 89.3s +**分析工具:** sqlfluff + +--- + +总计: 5 | 错误: 3 | 警告: 1 | 建议: 1 + +静态分析 · 3 个问题 + +- 🔴 `sqlfluff:LT14` L2 + L2:'GROUP' 关键字应始终另起一行。 + 建议: 将 GROUP BY 子句移到新行。例如:SELECT id FROM my_table +GROUP BY id; +- 🔴 `sqlfluff:LT15` L8 + L8:连续空行过多。 + 建议: 将第6-8行的连续空行减少为一个空行(删除第7和第8行)。 +- 🔴 `sqlfluff:LT12` L10 + L10:文件必须以单个尾随换行符结尾。 + 建议: 确保文件末尾只有一个换行符,删除多余的空行或在末尾添加一个换行符。 + +AI 审查 · 2 条建议 + +- 🟡 [AI] [bug] `inconsistent-query-semantics` L5 + **去重逻辑不一致** + 第2行使用 GROUP BY id 去重,而第5行和第9行使用 SELECT id 返回所有行,导致相同的表在不同行返回的行数可能不同。如果意图是去重,应统一使用 SELECT DISTINCT id; 如果意图是返回所有行,则第2行应去掉 GROUP BY。 + 建议: 统一使用 SELECT DISTINCT id FROM my_table; 以明确表达去重意图。 +- 🔵 [AI] [design] `group-by-without-aggregation` L2 + **GROUP BY 未使用聚合函数** + 第2行的 GROUP BY id 没有聚合函数,仅用于去重。使用 GROUP BY 去重可读性较差,且可能引入不必要的分组开销。建议使用 SELECT DISTINCT id 替代。 + 建议: 将 SELECT id FROM my_table GROUP BY id; 替换为 SELECT DISTINCT id FROM my_table;,以更高效、清晰地表达去重语义。 diff --git a/data/demo-sqlfluff/reports/05c_file_boundaries-review.md b/data/demo-sqlfluff/reports/05c_file_boundaries-review.md new file mode 100644 index 0000000..7ec942a --- /dev/null +++ b/data/demo-sqlfluff/reports/05c_file_boundaries-review.md @@ -0,0 +1,30 @@ +# 代码审查报告 + +**文件:** `data\demo-sqlfluff\src\05c_file_boundaries.sql` +**语言:** sql +**耗时:** 87.6s +**分析工具:** sqlfluff + +--- + +总计: 4 | 错误: 2 | 警告: 1 | 建议: 1 + +静态分析 · 2 个问题 + +- 🔴 `sqlfluff:LT13` L1 + 文件不能以换行或空白开头。 + 建议: 删除文件开头的所有空行或空白字符,确保 SQL 语句从第一行开始。 +- 🔴 `sqlfluff:LT12` L4 + 文件必须以单个尾随换行结束。 + 建议: 删除文件末尾多余的空行,只保留一个换行符。 + +AI 审查 · 2 条建议 + +- 🟡 [AI] [performance] `unbounded-query` L2 + **查询无过滤和限制,可能返回全部数据** + SELECT id FROM my_table 没有 WHERE 条件,也没有 LIMIT 子句。若表数据量大,会触发全表扫描并将全部 id 一次加载到应用内存,导致数据库和网络负载过高。 + 建议: 根据业务需要添加 WHERE 过滤条件(如 WHERE status = 'active'),并用 LIMIT 限制返回行数(如 LIMIT 1000)或采用分页/流式查询。 +- 🔵 [AI] [design] `unqualified-table-reference` L2 + **表名未限定 schema** + my_table 未使用 schema 前缀,在数据库搜索路径变化时可能解析到错误的表,降低可移植性与可维护性。 + 建议: 建议使用 schema.table 的形式,例如 public.my_table,以明确表归属。 diff --git a/data/demo-sqlfluff/reports/06_references-review.md b/data/demo-sqlfluff/reports/06_references-review.md new file mode 100644 index 0000000..56fd317 --- /dev/null +++ b/data/demo-sqlfluff/reports/06_references-review.md @@ -0,0 +1,53 @@ +# 代码审查报告 + +**文件:** `data\demo-sqlfluff\src\06_references.sql` +**语言:** sql +**耗时:** 155.4s +**分析工具:** sqlfluff + +--- + +总计: 11 | 错误: 8 | 警告: 2 | 建议: 1 + +静态分析 · 8 个问题 + +- 🔴 `sqlfluff:RF01` L3 + 第 3 行:引用 'missing_tbl.column_a' 指向在 FROM 子句(或上层语句)中不存在的表/视图。 + 建议: 若该列实际属于 my_table,请将引用改为 my_table.column_a;若确实需要查询 missing_tbl,请将其加入 FROM 子句,例如 JOIN missing_tbl ON ...。 +- 🔴 `sqlfluff:RF02` L9 + 第 9 行:SELECT 中存在未限定的引用 'column_a',但语句中引用了多张表/视图。 + 建议: 为该列添加表名或别名前缀:若来自 my_table,请使用 a.column_a;若来自 other_table,请使用 b.column_a。 +- 🔴 `sqlfluff:AM05` L12 + 第 12 行:JOIN 子句应使用完全限定名称。 + 建议: 确保 JOIN 条件中所有列都带表别名/表名限定,并将 JOIN 表表达式与其目标表放在同一行;推荐写法:JOIN other_table AS b ON a.id = b.ref_id。 +- 🔴 `sqlfluff:LT02` L13 + 第 13 行:'ON' 前应有换行,且 'ON' 前不应有缩进。 + 建议: 将 JOIN 条件和表表达式分开:先把 other_table AS b 放在 JOIN 所在行,再在下一行写 ON a.id = b.ref_id,例如 JOIN other_table AS b +ON a.id = b.ref_id;。 +- 🔴 `sqlfluff:RF04` L19 + 第 19 行:关键字不应作为标识符使用(此处将 'sum' 用作别名)。 + 建议: 将表别名从 sum 改为非关键字名称,例如 s 或 summary,并把 SELECT 中的 sum.a 改为 s.a。 +- 🔴 `sqlfluff:RF05` L23 + 第 23 行:标识符中不应包含特殊字符。 + 建议: 如果 'my-column' 是列名,请将其重命名为不含连字符的名称,如 my_column,然后使用 my_column;如果它只是字符串字面量,请改用单引号:'my-column'。 +- 🔴 `sqlfluff:RF06` L29 + 第 29 行:标识符 "SIMPLE_COL" 被不必要地加了引号。 + 建议: 直接去掉双引号:SELECT SIMPLE_COL FROM my_table;。 +- 🔴 `sqlfluff:LT12` L32 + 第 32 行:文件必须以单个换行符结束。 + 建议: 检查文件末尾,确保最后一行后只有一个换行符;删除多余的空白行或在缺少换行符的文件末尾补一个换行。 + +AI 审查 · 3 条建议 + +- 🟡 [AI] [design] `demo-run-error-aborts-script` L3 + **起始语句是无效引用,整个演示脚本会在第一条语句处终止** + 第 3 行引用 missing_tbl.column_a,但 FROM 子句中只有 my_table。若按文件顺序执行,多数 SQL 引擎会在第一条语句报错并停止,后续用于演示 RF02/RF04/RF05/RF06 的语句都无法执行。 + 建议: 将故意写错的语句放到文件末尾,或为每个演示准备独立测试用例/事务,并捕获预期错误;确保合法语句先执行,非法语句最后执行。 +- 🟡 [AI] [bug] `quoted-identifier-case-sensitivity` L29 + **带引号的标识符 SIMPLE_COL 是大小写敏感的,可能匹配不到实际列** + 双引号标识符在多数数据库中按精确大小写匹配。如果 my_table 中实际列名是 simple_col 或 Simple_Col,SELECT "SIMPLE_COL" 会失败;RF06 只提示引号多余,未覆盖这一大小写风险。 + 建议: 先确认数据字典中的真实列名。若列名确为 SIMPLE_COL,直接去掉引号;否则改成与实际列名一致的大小写并去掉引号。 +- 🔵 [AI] [design] `demo-comment-incomplete` L1 + **演示注释未列出实际触发的全部规则** + 第 1 行注释声明仅演示 RF01、RF02、RF04、RF05、RF06,但静态分析还发现 AM05、LT02、LT12;如果这是规则演示文件,注释不完整且会误导维护者。 + 建议: 更新注释以列出所有实际触发的规则,或修复 AM05/LT02/LT12 等非预期问题,使文件只演示声明的规则。 diff --git a/data/demo-sqlfluff/reports/07_structure-review.md b/data/demo-sqlfluff/reports/07_structure-review.md new file mode 100644 index 0000000..a5dfd1d --- /dev/null +++ b/data/demo-sqlfluff/reports/07_structure-review.md @@ -0,0 +1,111 @@ +# 代码审查报告 + +**文件:** `data\demo-sqlfluff\src\07_structure.sql` +**语言:** sql +**耗时:** 226.0s +**分析工具:** sqlfluff + +--- + +总计: 30 | 错误: 29 | 警告: 1 | 建议: 0 + +静态分析 · 28 个问题 + +- 🔴 `sqlfluff:LT05` L1 + L1: 行过长(97 > 80)。 + 建议: 将该行注释拆分为多行或精简文字,使长度不超过 80 个字符。 +- 🔴 `sqlfluff:ST01` L3 + L3: 不要在 CASE WHEN 语句中指定 'ELSE NULL'(冗余)。 + 建议: 删除 `ELSE NULL` 分支;当条件不满足时 CASE 会自然返回 NULL。 +- 🔴 `sqlfluff:ST02` L13 + L13: 不必要的 CASE 语句,建议改用 COALESCE 函数。 + 建议: 改为 `COALESCE(amount > 0, FALSE) AS positive`,效果与当前 CASE 一致。 +- 🔴 `sqlfluff:ST03` L20 + L20: 查询定义了 CTE "unused_cte" 但未使用。 + 建议: 删除未使用的 CTE `WITH unused_cte AS (...)`,直接从 `SELECT` 开始查询。 +- 🔴 `sqlfluff:LT08` L23 + L23: CTE 结束括号之后应有空行,但未找到。 + 建议: 在 CTE 的闭合括号 `)` 与后续 `SELECT` 之间插入一个空行。 +- 🔴 `sqlfluff:ST04` L32 + L32: ELSE 子句中的嵌套 CASE 语句可以展平。 + 建议: 合并为一个 CASE:`CASE WHEN a = 1 THEN b WHEN c = 1 THEN d END AS result`。 +- 🔴 `sqlfluff:RF04` L35 + L35: 不应将关键字用作标识符。 + 建议: 将别名 `result` 改为非关键字名称,例如 `result_value`。 +- 🔴 `sqlfluff:AL08` L42 + L42: 列别名 'id' 与第 41 行的列名重复。 + 建议: 为 `sub.id` 指定唯一别名,例如 `AS sub_id`,避免结果集列名冲突。 +- 🔴 `sqlfluff:AM05` L45 + L45: JOIN 子句应使用全限定名。 + 建议: 明确使用 `INNER JOIN`,并确保 ON 条件中的列均带表别名限定。 +- 🔴 `sqlfluff:ST05` L46 + L46: select_statement 子句中不应包含子查询,应改用 CTE。 + 建议: 将 JOIN 中的派生表提取为 CTE:`WITH sub AS (SELECT id FROM other_table) SELECT ... JOIN sub ON ...`。 +- 🔴 `sqlfluff:LT02` L46 + L46: 应在 'ON' 前换行,且 'ON' 前不应有缩进。 + 建议: 将 ON 子句放到新行,放在与 JOIN 同一层级:`(SELECT ...) AS sub +ON a.id = sub.id;` +- 🔴 `sqlfluff:ST06` L49 + L49: SELECT 中应先写通配符和简单目标,再写计算与聚合。 + 建议: 将 `*` 移到最前,再写普通列,最后写 `SUM(amount)`,并给聚合结果命名。注意当前查询还缺少 GROUP BY 或窗口函数。 +- 🔴 `sqlfluff:AL03` L50 + L50: 列表达式没有别名,请使用显式 `AS` 子句。 + 建议: 为 `SUM(amount)` 添加别名:`SUM(amount) AS total_amount`。 +- 🔴 `sqlfluff:AM05` L62 + L62: JOIN 子句应使用全限定名。 + 建议: 将 `USING (id)` 改写为带表别名限定的 ON 条件,并可加上 `INNER`。 +- 🔴 `sqlfluff:LT02` L63 + L63: 应在 'USING' 前换行,且不应缩进。 + 建议: 将 `USING (id)` 移到新行(若改写为 ON 也保持换行)。 +- 🔴 `sqlfluff:ST07` L63 + L63: 发现 USING 语句,应只使用 ON 语句。 + 建议: 将 `USING (id)` 改写为 `ON a.id = b.id`。 +- 🔴 `sqlfluff:LT10` L66 + L66: 'SELECT' 修饰符(如 'DISTINCT')必须与 'SELECT' 在同一行。 + 建议: 将 `DISTINCT` 移到 `SELECT` 同一行:`SELECT DISTINCT id`。 +- 🔴 `sqlfluff:LT02` L67 + L67: 该行不应缩进。 + 建议: 将 `DISTINCT` 移到 `SELECT` 同一行,消除缩进。 +- 🔴 `sqlfluff:LT01` L67 + L67: 'DISTINCT' 关键字与左括号 '(' 之间应有单个空格。 + 建议: 不要使用函数式写法;最好是去掉括号写成 `DISTINCT id`。 +- 🔴 `sqlfluff:ST08` L67 + L67: 'DISTINCT' 不能与括号一起使用。 + 建议: 去掉括号:`SELECT DISTINCT id`。 +- 🔴 `sqlfluff:AM05` L77 + L77: JOIN 子句应使用全限定名。 + 建议: 明确写出 `INNER JOIN`,并保持 ON 条件使用表别名限定。 +- 🔴 `sqlfluff:LT02` L78 + L78: 应在 'ON' 前换行,且不应缩进。 + 建议: 将 `ON` 子句放到新行:`other_table AS b +ON b.id = a.id;` +- 🔴 `sqlfluff:ST09` L78 + L78: JOIN 条件应把先出现的表放在前面。 + 建议: 将 `ON b.id = a.id` 调整为 `ON a.id = b.id`。 +- 🔴 `sqlfluff:ST10` L86 + L86: 冗余常量表达式。 + 建议: 删除 `WHERE` 子句,或改为 `WHERE TRUE`;如果动态生成条件,请不要保留恒真条件。 +- 🔴 `sqlfluff:ST11` L94 + L94: JOIN 表 'other_table AS b' 未在查询其他位置引用。 + 建议: 若确实不需要该表,删除整个 LEFT JOIN;若只想判断存在性,请改用 EXISTS 子查询。 +- 🔴 `sqlfluff:LT02` L94 + L94: 应在 'ON' 前换行,且不应缩进。 + 建议: 将 `ON` 子句放到新行:`other_table AS b +ON a.id = b.id;` +- 🔴 `sqlfluff:ST12` L100 + L100: 检测到连续分号(计数 2)。 + 建议: 删除多余的分号,只保留一个 `;`。 +- 🔴 `sqlfluff:LT12` L101 + L101: 文件必须以单个尾随换行符结束。 + 建议: 确保文件末尾恰好有一个换行符,不要多出空行或缺少换行。 + +AI 审查 · 2 条建议 + +- 🔴 [AI] [bug] `aggregate-without-group-by` L49 + **聚合函数与未分组列/通配符混用** + 第49行的 SELECT 将 `SUM(amount)` 与 `id`、`*` 放在同一个 SELECT 列表中,但没有 GROUP BY,也没有把 `SUM` 改为窗口函数。在大多数 SQL 引擎中这是非法查询;即使某些引擎允许,也会随机返回非聚合列的任意行,结果不可预测。 + 建议: 如果希望保留每一行并附带总额,使用窗口函数 `SUM(amount) OVER () AS total_amount`;如果希望做聚合,则需要列出所有非聚合列并加 `GROUP BY`,或显式只保留聚合结果。 +- 🟡 [AI] [bug] `unused-left-join-cardinality` L94 + **未使用的 LEFT JOIN 可能使结果行数倍增** + 第93-94行的 LEFT JOIN 虽然未在 SELECT 或 WHERE 中引用 `other_table AS b`,但 SQL 仍会执行关联;若 `other_table.id` 有重复值,每条匹配都会复制左表行,导致结果集意外膨胀。静态规则只报告了表未被引用,但没有强调行数变化风险。 + 建议: 完全移除该 LEFT JOIN;若需要检查是否存在匹配,使用 `WHERE EXISTS (SELECT 1 FROM other_table b WHERE b.id = a.id)`。 diff --git a/data/demo-sqlfluff/reports/08_jinja-review.md b/data/demo-sqlfluff/reports/08_jinja-review.md new file mode 100644 index 0000000..c3cf614 --- /dev/null +++ b/data/demo-sqlfluff/reports/08_jinja-review.md @@ -0,0 +1,30 @@ +# 代码审查报告 + +**文件:** `data\demo-sqlfluff\src\08_jinja.sql` +**语言:** sql +**耗时:** 56.7s +**分析工具:** sqlfluff + +--- + +总计: 4 | 错误: 2 | 警告: 1 | 建议: 1 + +静态分析 · 2 个问题 + +- 🔴 `sqlfluff:JJ01` L2 + 第2行:Jinja 标签应在两侧各有一个空格:{{1}} + 建议: 将 {{1}} 改为 {{ 1 }},即表达式与花括号之间各留一个空格。 +- 🔴 `sqlfluff:LT12` L6 + 第6行:文件必须以单个尾随换行符结尾。 + 建议: 在文件末尾(当前最后一行 my_table; 后)补一个换行符,并确保没有多余空行。 + +AI 审查 · 2 条建议 + +- 🟡 [AI] [performance] `unfiltered-select-all` L3 + **查询未加过滤条件,可能全表扫描并返回全部数据** + 第3~6行的 SELECT 仅选择 id 且没有 WHERE 或 LIMIT;若直接用于生产,会读取 my_table 全表数据,可能造成性能问题,同时将全部 id 暴露给调用方。 + 建议: 如果只需要部分数据,增加明确的 WHERE 条件(如按时间或状态过滤);若用于分页/展示,应添加 LIMIT;若确实需要全量 id,请补充注释说明用途及执行窗口。 +- 🔵 [AI] [design] `unqualified-table-reference` L6 + **表名未限定 schema/database** + 第6行直接使用 my_table,未加 schema 或 database 限定;当数据库搜索路径变化时,可能解析到非预期表,导致查询错误。 + 建议: 将表名改为明确的限定名,例如 my_schema.my_table(按实际环境替换 schema 名)。 diff --git a/data/demo-sqlfluff/reports/09_aliasing_length-review.md b/data/demo-sqlfluff/reports/09_aliasing_length-review.md new file mode 100644 index 0000000..5cacee2 --- /dev/null +++ b/data/demo-sqlfluff/reports/09_aliasing_length-review.md @@ -0,0 +1,31 @@ +# 代码审查报告 + +**文件:** `data\demo-sqlfluff\src\09_aliasing_length.sql` +**语言:** sql +**耗时:** 132.7s +**分析工具:** sqlfluff + +--- + +总计: 4 | 错误: 1 | 警告: 1 | 建议: 2 + +静态分析 · 1 个问题 + +- 🔴 `sqlfluff:AL06` L8 + L8: 别名长度不应超过30个字符。 + 建议: 将别名 this_alias_is_way_longer_than_thirty_characters_ok 缩短为不超过30字符的语义化名称(如 long_alias),并确保第6行和第8行的引用同步更新。 + +AI 审查 · 3 条建议 + +- 🟡 [AI] [design] `repeated-alias-reference` L6 + **长别名在 SELECT 和 FROM 中重复出现,修改时易遗漏** + 同一个超长别名在 SELECT 子句(第6行)和 FROM 子句(第8行)中出现了两次。静态分析仅标记第8行的定义超长,但修复时若只修改定义处而遗漏引用处,SQL 会因别名不匹配而失败。 + 建议: 将 this_alias_is_way_longer_than_thirty_characters_ok 统一替换为简短别名(如 long_alias),并同时更新两处引用。 +- 🔵 [AI] [performance] `missing-where-clause` L8 + **缺少 WHERE 子句可能导致全表扫描** + 查询未包含 WHERE 或 LIMIT,会读取并返回 my_table 的全部行。当表数据量大时,可能产生较高的 I/O 和网络开销。 + 建议: 根据业务需求添加过滤条件(例如 WHERE id > ?),或添加 LIMIT 限制返回行数。 +- 🔵 [AI] [design] `missing-order-by` L8 + **缺少 ORDER BY 导致结果顺序不确定** + 查询未指定排序字段,数据库返回结果集的顺序可能因执行计划而变化,影响结果的可预测性。 + 建议: 在查询末尾添加 ORDER BY this_alias_is_way_longer_than_thirty_characters_ok.id(或根据业务选择排序列)。 diff --git a/data/demo-sqlfluff/reports/demo-sqlfluff插件实测覆盖率报告.md b/data/demo-sqlfluff/reports/demo-sqlfluff插件实测覆盖率报告.md new file mode 100644 index 0000000..c747d4c --- /dev/null +++ b/data/demo-sqlfluff/reports/demo-sqlfluff插件实测覆盖率报告.md @@ -0,0 +1,148 @@ +# demo-sqlfluff 插件实测覆盖率报告 + +> **工程**:`demo-sqlfluff`(11 个 SQL 样例文件,无 `.sqlfluff` 工程配置 → 插件回退内置配置) +> **测试对象**:插件 SQLFluff 适配器(实测方言 ansi,内置规则集 `core` + 25 条精选 = 57 条,`max_alias_length = 30`) +> **实测日期**:2026-08-25 +> **比对基线**:sqlfluff 4.3.0 + 插件内置配置逐字复刻(比对键 = 规则码 + 行号) + +--- + +## 一、核心结论 + +| 指标 | 结果 | 状态 | +|---|---|---| +| 规则覆盖率(内置启用 57 条) | **57/57 种全部触发(100%)** | ✅ 全覆盖 | +| 诊断总数 | **113 处**(与基线完全一致) | ✅ | +| 逐条一致性(规则 + 行号) | **113/113 条完全一致(零偏差)** | ✅ | +| 解析错误(PRS) | **0 处** | ✅ | +| 插件缺陷 | **0 个**(JJ01 行号缺陷已修复并复测验证) | ✅ | + +**实测完全达成全覆盖基准**:插件在「工程无配置文件 → 回退内置配置」路径下,内置启用的 57 条规则全部触发、113 处诊断与模拟基线逐条零偏差。此前发现的 **JJ01 行号显示 LNaN 缺陷已修复**——适配器对 sqlfluff JSON 中缺失 `end_line_no` 字段的回退已生效,复测 `08_jinja.sql` 确认 JJ01 正确显示为 `L2`。**SQLFluff 适配器静态分析链路验证通过,缺陷清零。** + +--- + +## 二、逐文件比对(实测 vs 基线) + +| 文件 | 实测 | 基线 | 一致性 | 覆盖规则 | +|---|---:|---:|---|---| +| `01_aliasing.sql` | 9 | 9 | ✅ 逐条一致 | AL02/AL04/AL05/AL08/AL09/AL10、AM05、LT02、LT12 | +| `02_ambiguity.sql` | 10 | 10 | ✅ 逐条一致 | AM01/AM02/AM03/AM05/AM06/AM08、AL05、LT02、LT12 | +| `03_caps.sql` | 25 | 25 | ✅ 逐条一致 | CP01-CP05、AL03、LT12 | +| `04_convention.sql` | 13 | 13 | ✅ 逐条一致 | CV01-CV06/CV08/CV12、AM05、LT02、ST11、LT12 | +| `05_layout.sql` | 12 | 12 | ✅ 逐条一致 | LT01/LT02/LT05-LT08/LT11/LT12、AM02 | +| `05b_layout_misc.sql` | 3 | 3 | ✅ 逐条一致 | LT14、LT15、LT12 | +| `05c_file_boundaries.sql` | 2 | 2 | ✅ 逐条一致 | LT13、LT12 | +| `06_references.sql` | 8 | 8 | ✅ 逐条一致 | RF01/RF02/RF04/RF05/RF06、AM05、LT02、LT12 | +| `07_structure.sql` | 28 | 28 | ✅ 逐条一致 | ST01-ST12、LT01/LT02/LT05/LT08/LT10、AL03/AL08、AM05、RF04、LT12 | +| `08_jinja.sql` | 2 | 2 | ✅ 逐条一致 | JJ01(L2,复测确认修复生效)、LT12 | +| `09_aliasing_length.sql` | 1 | 1 | ✅ 逐条一致 | AL06 | +| **合计** | **113** | **113** | **✅ 零偏差(113/113)** | **57/57 种** | + +> 比对方法:将插件报告中每条静态诊断(规则码 + 行号)与基线做多重集合比对;11 个文件全部 `missing = 0`、`extra = 0`(含 JJ01 行号缺陷修复后的复测确认)。 + +--- + +## 三、规则分组覆盖(57/57 = 100%) + +| 分组 | 规则数 | 触发规则明细 | +|---|---:|---| +| AL(aliasing 别名) | 8 | AL02、AL03、AL04、AL05、AL06、AL08、AL09、AL10 | +| AM(ambiguous 歧义) | 6 | AM01、AM02、AM03、AM05、AM06、AM08 | +| CP(capitalisation 大小写) | 5 | CP01、CP02、CP03、CP04、CP05 | +| CV(convention 约定) | 8 | CV01、CV02、CV03、CV04、CV05、CV06、CV08、CV12 | +| JJ(jinja 模板) | 1 | JJ01 | +| LT(layout 布局) | 12 | LT01、LT02、LT05、LT06、LT07、LT08、LT10、LT11、LT12、LT13、LT14、LT15 | +| RF(references 引用) | 5 | RF01、RF02、RF04、RF05、RF06 | +| ST(structure 结构) | 12 | ST01-ST12 全部 | + +**严重级别**:113 处全部为 error(🔴)——内置启用的 57 条规则在插件分级表中均为 P0/P1 级(P0 × 32 + P1 × 25),与实测全 error 一致。 + +**诊断分布**:`07_structure.sql` 28 处最多,其次 `03_caps.sql` 25 处、`04_convention.sql` 13 处、`05_layout.sql` 12 处。 + +> 注:插件报告中另有 AI 审查建议 29 条(03_caps 4 / 05_layout 4 / 01_aliasing 3 / 02_ambiguity 3 / 06_references 3 / 09_aliasing_length 3 / 05b 2 / 05c 2 / 07_structure 2 / 04_convention 2 / 08_jinja 1),属 AI 增强能力,不计入静态规则覆盖口径。 + +--- + +## 四、全覆盖的达成要点 + +内置规则集(`core` 组 31 条 + 精选追加 25 条,合并去重 57 条)中有两条规则需要特别条件,本轮均已触发: + +1. **AM02(ambiguous.union,裸 UNION 应写明 DISTINCT/ALL)**:方言门控规则,仅对 ansi/bigquery/mysql/snowflake 等支持 `UNION DISTINCT` 语法的方言生效,oracle 不在白名单。实测机按指引将插件设置 `sqlfluff.dialect` 设为 **ansi**,AM02 在 `02_ambiguity.sql:20` 与 `05_layout.sql:41` 正常触发(若用默认 oracle 方言,该规则不报,覆盖率为 56/57)。 +2. **AL06(aliasing.length,别名长度上限)**:内置配置设 `max_alias_length = 30`,由补充样例 `09_aliasing_length.sql` 的 51 字符别名触发(实测报在 L8 别名定义处)。 + +**实测环境判定**:实测报告路径 `data\demo-sqlfluff\src\` 为单层目录,11 个文件与当前工作区版本一致(`02_ambiguity.sql` 顶部含 5 行方言说明注释、`09_aliasing_length.sql` 存在);方言判定为 ansi 的依据:AM02 触发 + 全部 113 处行号与 ansi 基线逐条吻合(oracle 基线为 111 处且无 AM02)。 + +--- + +## 五、插件缺陷记录:JJ01 行号 LNaN(已修复验证 ✅) + +**发现过程**:首轮实测中 `08_jinja.sql` 报告显示 `` `sqlfluff:JJ01` LNaN ``——规则触发正确,但行号显示为 NaN(真实位置 L2)。 + +**根因链**(已逐环节实证): + +1. **sqlfluff JSON 输出**:JJ01 违规项(jinja 模板类违规)只有 `start_line_no: 2` / `start_line_pos: 1`,**顶层缺失 `end_line_no` / `end_line_pos` 字段**;其余 112 处违规均有完整 end 字段(已对 11 个文件全量验证:缺失 end 字段的违规仅 JJ01 这 1 条)。 +2. **插件适配器**(`sqlfluff.ts`)直接计算:`new vscode.Range(v.start_line_no - 1, v.start_line_pos - 1, v.end_line_no - 1, v.end_line_pos - 1)` → end 传入 `undefined - 1 = NaN`。 +3. **vscode.Range 构造函数**:`start.isBefore(end)` 对 NaN 比较恒为 false → **交换 start/end** → `range.start` 变成 `Position(NaN, NaN)`。 +4. **报告生成器**(`report.ts`)读取 `diag.range.start.line` → `L${NaN + 1}` → 显示 "LNaN"。 + +**修复方案**(适配器侧对缺失 end 字段回退到 start 坐标): + +```typescript +const endLine = (v.end_line_no ?? v.start_line_no) - 1; +const endPos = (v.end_line_pos ?? v.start_line_pos) - 1; +``` + +**修复验证**(2026-08-25 复测 `08_jinja.sql`): + +| 项 | 修复前 | 修复后 | +|---|---|---| +| `sqlfluff:JJ01` 行号 | `LNaN` | **`L2`** ✅ | +| 建议文案 | 正常(`Jinja 标签两侧应各有一个空格:{{1}}`) | 正常(新增行号前缀「第2行:」) | +| `sqlfluff:LT12` | L6 ✅ | L6 ✅ | +| 文件诊断合计 | 2 处 | 2 处 ✅ 与基线逐条一致 | + +修复后 `08_jinja.sql` 复测 2/2 条与基线(JJ01 L2 + LT12 L6,sqlfluff 4.3.0 复现)逐条一致,demo-sqlfluff 整体升至 **113/113 条零偏差**。 + +--- + +## 六、工程数据勘误(coverage_map.json) + +实测过程中发现工程自带的 `coverage_map.json` 存在 10 处陈旧行号(工程蓝图数据问题,非插件问题,已修正): + +| 类型 | 涉及条目 | 原因 | +|---|---|---| +| `02_ambiguity.sql` 行号整体 +4 | AM01(2→6)、AM02(16→20)、AM03(28→32)、AM05(38→42, 59→63)、AM06(50→54)、AM08(59→63)、AL05(60→64)、LT02(39→43)、LT12(61→65) | 文件顶部补充 5 行方言说明注释后未同步更新映射 | +| AL06 行号 | 6→8 | sqlfluff 实际报在 FROM 别名定义处(L8),蓝图误记为 SELECT 引用处(L6) | + +--- + +## 七、数据口径说明 + +| 项 | 说明 | +|---|---| +| 覆盖率口径 | 57 条 = 插件内置配置启用规则数(`core` 组 31 + 精选追加 25,合并去重);规则触发即计入,与 P0/P1 分级(32+25=57)完全对应 | +| 方言口径 | 实测为 **ansi**(按上轮指引设置);默认 oracle 下 AM02 不触发(56/57 = 98.2%),属 sqlflutt 方言能力差异而非插件缺陷 | +| 比对引擎 | sqlfluff 4.3.0(4.2.2 实测行为一致:同为 113 处、行号相同、JJ01 同样缺失 end 字段),内置配置逐字复刻插件 `buildBuiltinSqlfluffConfig` | +| 基线数据 | `/data/user/work/results/sqlfluff-final-compare.json`(逐文件比对明细);`/data/user/work/results/sqlfluff-actual-parsed.json`(实测解析结果) | +| 已知差异 | 无(JJ01 行号缺陷已修复并复测验证:LNaN → L2;113/113 条规则码 + 行号零偏差) | + +--- + +## 八、结论与后续 + +**demo-sqlfluff 工程的插件实测验证完成**: + +1. ✅ 内置规则全覆盖达成(57/57 种 · 113 处 · 零解析错误) +2. ✅ 插件行为零偏差(113/113 条逐条一致,含 JJ01 缺陷修复后的复测确认) +3. ✅ 插件缺陷闭环:JJ01 行号 LNaN → 修复 → 复测验证 L2(发现、定位、修复、验证全流程完成) +4. ✅ 「内置配置回退」路径验证通过(工程无 `.sqlfluff` 配置文件场景) + +**后续建议**: + +- ~~插件侧修复 JJ01~~(✅ 已完成并验证) +- **其他工程**:`demo-pmd` 按同样方法实测(先本地基线复现 → 实测逐条比对),注意 Java 版本与 ruleset +- **可选**:若需验证「工程配置优先」路径,可在工程根添加 `.sqlfluff` 配置文件复测(预期插件改用工程配置) + +--- + +*报告生成于 2026-08-25 · demo-sqlfluff 插件实测 · 比对脚本:`sqlfluff-final-compare.mjs` · 数据:`sqlfluff-final-compare.json`* diff --git a/data/demo-sqlfluff/src/01_aliasing.sql b/data/demo-sqlfluff/src/01_aliasing.sql new file mode 100644 index 0000000..6cc3f3c --- /dev/null +++ b/data/demo-sqlfluff/src/01_aliasing.sql @@ -0,0 +1,47 @@ +-- Demo: aliasing rules (AL02, AL03, AL04, AL05, AL08, AL09, AL10) +-- AL06 样例见 09_aliasing_length.sql:内置配置 max_alias_length = 30, +-- 别名超长即可触发(min_alias_length 未设置,短别名不检查)。 +SELECT + col1 col1_alias +FROM + my_table; +-- AL02: column alias should use explicit AS + +SELECT + SUM(amount) +FROM + my_table; +-- AL03: column expression without alias + +SELECT + t1.id +FROM + my_table AS t1 +JOIN + other_table AS t1 ON t1.id = other_table.ref_id; +-- AL04: duplicate table alias + +SELECT + id +FROM + my_table AS unused_alias; +-- AL05: table alias never used + +SELECT + id AS shared_alias, + name AS shared_alias +FROM + my_table; +-- AL08: duplicate column alias + +SELECT + id AS id +FROM + my_table; +-- AL09: column alias identical to column name + +SELECT + cnt +FROM + (SELECT COUNT(*) AS cnt FROM my_table); +-- AL10: derived table must have an alias \ No newline at end of file diff --git a/data/demo-sqlfluff/src/02_ambiguity.sql b/data/demo-sqlfluff/src/02_ambiguity.sql new file mode 100644 index 0000000..def59b6 --- /dev/null +++ b/data/demo-sqlfluff/src/02_ambiguity.sql @@ -0,0 +1,65 @@ +-- Demo: ambiguity rules (AM01, AM02, AM03, AM05, AM06, AM08) +-- 注:AM02(ambiguous.union)为方言门控规则——规则仅对 ansi/bigquery/mysql +-- 等支持 UNION DISTINCT 的方言生效;插件默认方言 oracle 不在白名单内, +-- 默认设置下本样例不会报 AM02。手动测试时将 sqlfluff.dialect 设为 ansi +-- 即可触发(经实测 ansi 方言下启用规则 57/57 全覆盖)。 +SELECT DISTINCT + dept, + region +FROM + my_table +GROUP BY + dept, + region; +-- AM01: DISTINCT combined with GROUP BY + +SELECT + id +FROM + my_table +UNION +SELECT + id +FROM + other_table; +-- AM02: bare UNION instead of UNION ALL/DISTINCT + +SELECT + a, + b +FROM + my_table +ORDER BY + a, + b DESC; +-- AM03: mixed explicit/implicit ordering directions + +SELECT + a.id, + b.name +FROM + my_table AS a +JOIN + other_table AS b ON a.id = b.id; +-- AM05: JOIN clause not fully qualified (needs INNER) + +SELECT + a, + b +FROM + my_table +GROUP BY + a, + b +ORDER BY + 1, + 2; +-- AM06: inconsistent column references (GROUP BY names vs ORDER BY positions) + +SELECT + a.id +FROM + my_table AS a +JOIN + other_table AS b; +-- AM08: implicit cross join (JOIN without ON condition) \ No newline at end of file diff --git a/data/demo-sqlfluff/src/03_caps.sql b/data/demo-sqlfluff/src/03_caps.sql new file mode 100644 index 0000000..c64cce6 --- /dev/null +++ b/data/demo-sqlfluff/src/03_caps.sql @@ -0,0 +1,40 @@ +-- Demo: capitalisation rules (CP01, CP02, CP03, CP04, CP05) +select + id, + status_code +from + my_table +where + status_code = 'active'; +-- CP01: lowercase keywords + +SELECT + colA, + other_col +FROM + my_table +WHERE + coLa = 1; +-- CP02: inconsistent capitalisation of identifiers + +SELECT + SUM(amount), + sum(quantity) +FROM + my_table; +-- CP03: inconsistent capitalisation of function names + +SELECT + id +FROM + my_table +WHERE + flag IS NULL OR other_flag IS null; +-- CP04: inconsistent capitalisation of boolean/null literals + +SELECT + CAST(id AS VARCHAR), + CAST(amount AS varchar) +FROM + my_table; +-- CP05: inconsistent capitalisation of datatypes \ No newline at end of file diff --git a/data/demo-sqlfluff/src/04_convention.sql b/data/demo-sqlfluff/src/04_convention.sql new file mode 100644 index 0000000..6923837 --- /dev/null +++ b/data/demo-sqlfluff/src/04_convention.sql @@ -0,0 +1,63 @@ +-- Demo: convention rules (CV01, CV02, CV03, CV04, CV05, CV08, CV12, CV06) +SELECT + id +FROM + my_table +WHERE + status_code != 'active' OR status_code <> 'pending'; +-- CV01: inconsistent "not equal to" operator + +SELECT + IFNULL(amount, 0) AS amount_or_zero +FROM + my_table; +-- CV02: use COALESCE instead of IFNULL + +SELECT + id, + name, +FROM + my_table; +-- CV03: trailing comma in select clause + +SELECT + COUNT(1) AS total_rows +FROM + my_table; +-- CV04: use COUNT(*) instead of COUNT(1) + +SELECT + id +FROM + my_table +WHERE + end_date = NULL; +-- CV05: comparisons with NULL should use IS + +SELECT + a.id +FROM + my_table AS a +RIGHT JOIN + other_table AS b ON a.id = b.ref_id; +-- CV08: use LEFT JOIN instead of RIGHT JOIN + +SELECT + a.id, + b.name +FROM + my_table AS a +JOIN + other_table AS b +WHERE + a.id = b.ref_id; +-- CV12: use JOIN ... ON ... instead of WHERE for join conditions + +SELECT + id +FROM + my_table +WHERE + status_code = 'active' +; +-- CV06: statement terminator on its own line (should follow the statement) \ No newline at end of file diff --git a/data/demo-sqlfluff/src/05_layout.sql b/data/demo-sqlfluff/src/05_layout.sql new file mode 100644 index 0000000..1e9f22e --- /dev/null +++ b/data/demo-sqlfluff/src/05_layout.sql @@ -0,0 +1,42 @@ +-- Demo: layout rules (LT01, LT02, LT05, LT06, LT07, LT08, LT10, LT11) +SELECT * FROM my_table; +-- LT01: extra spacing after SELECT + + SELECT + id +FROM + my_table; +-- LT02: inappropriate indentation + +SELECT + id, + a_very_long_column_name_that_pushes_the_total_line_length_beyond_the_allowed_maximum_eighty +FROM + my_table; +-- LT05: line too long + +SELECT + SUM (amount) AS total_amount +FROM + my_table; +-- LT06: function name not immediately followed by parenthesis + +WITH cte AS ( + SELECT id FROM my_table) +SELECT * FROM cte; +-- LT07: CTE closing bracket should be on a new line + +WITH cte AS ( + SELECT id FROM my_table +) +SELECT * FROM cte; +-- LT08: blank line expected after CTE closing bracket + +SELECT DISTINCT + id +FROM + my_table; +-- LT10: SELECT modifier must be on same line + +SELECT id FROM my_table UNION SELECT id FROM other_table; +-- LT11: set operators should be surrounded by newlines \ No newline at end of file diff --git a/data/demo-sqlfluff/src/05b_layout_misc.sql b/data/demo-sqlfluff/src/05b_layout_misc.sql new file mode 100644 index 0000000..b839a32 --- /dev/null +++ b/data/demo-sqlfluff/src/05b_layout_misc.sql @@ -0,0 +1,10 @@ +-- Demo: layout rules (LT14, LT15) +SELECT id FROM my_table GROUP BY id; +-- LT14: GROUP keyword should start a new line + +SELECT id FROM my_table; + + + +SELECT id FROM my_table; +-- LT15: too many consecutive blank lines \ No newline at end of file diff --git a/data/demo-sqlfluff/src/05c_file_boundaries.sql b/data/demo-sqlfluff/src/05c_file_boundaries.sql new file mode 100644 index 0000000..df3b60d --- /dev/null +++ b/data/demo-sqlfluff/src/05c_file_boundaries.sql @@ -0,0 +1,4 @@ + +SELECT id FROM my_table; + + diff --git a/data/demo-sqlfluff/src/06_references.sql b/data/demo-sqlfluff/src/06_references.sql new file mode 100644 index 0000000..56b4ab5 --- /dev/null +++ b/data/demo-sqlfluff/src/06_references.sql @@ -0,0 +1,32 @@ +-- Demo: references rules (RF01, RF02, RF04, RF05, RF06) +SELECT + missing_tbl.column_a +FROM + my_table; +-- RF01: reference to object not present in FROM + +SELECT + column_a +FROM + my_table AS a +JOIN + other_table AS b ON a.id = b.ref_id; +-- RF02: unqualified column with multiple referenced tables + +SELECT + sum.a +FROM + my_table AS sum; +-- RF04: keyword used as identifier (alias) + +SELECT + "my-column" +FROM + my_table; +-- RF05: special characters in identifier + +SELECT + "SIMPLE_COL" +FROM + my_table; +-- RF06: unnecessary quoted identifier \ No newline at end of file diff --git a/data/demo-sqlfluff/src/07_structure.sql b/data/demo-sqlfluff/src/07_structure.sql new file mode 100644 index 0000000..6c17ac3 --- /dev/null +++ b/data/demo-sqlfluff/src/07_structure.sql @@ -0,0 +1,101 @@ +-- Demo: structure rules (ST01, ST02, ST03, ST04, ST05, ST06, ST07, ST08, ST09, ST10, ST11, ST12) +SELECT + CASE + WHEN status_code = 'active' THEN 1 + ELSE NULL + END AS is_active +FROM + my_table; +-- ST01: do not specify 'else null' + +SELECT + CASE + WHEN amount > 0 THEN TRUE + ELSE FALSE + END AS positive +FROM + my_table; +-- ST02: unnecessary CASE statement + +WITH unused_cte AS ( + SELECT id FROM my_table +) +SELECT + id +FROM + my_table; +-- ST03: unused CTE + +SELECT + CASE + WHEN a = 1 THEN b + ELSE CASE + WHEN c = 1 THEN d + END + END AS result +FROM + my_table; +-- ST04: nested CASE in ELSE clause + +SELECT + a.id, + sub.id +FROM + my_table AS a +JOIN + (SELECT id FROM other_table) AS sub ON a.id = sub.id; +-- ST05: subquery in JOIN clause + +SELECT + SUM(amount), + id, + * +FROM + my_table; +-- ST06: column order (aggregate before wildcard/simple) + +SELECT + a.id, + b.name +FROM + my_table AS a +JOIN + other_table AS b USING (id); +-- ST07: prefer join keys instead of USING + +SELECT + DISTINCT(id) +FROM + my_table; +-- ST08: DISTINCT used with parentheses + +SELECT + a.id, + b.name +FROM + my_table AS a +JOIN + other_table AS b ON b.id = a.id; +-- ST09: join condition order (later table listed first) + +SELECT + id +FROM + my_table +WHERE + 2 = 2; +-- ST10: redundant constant expression + +SELECT + a.id +FROM + my_table AS a +LEFT JOIN + other_table AS b ON a.id = b.id; +-- ST11: unused join + +SELECT + id +FROM + my_table;; +-- ST12: consecutive semicolons \ No newline at end of file diff --git a/data/demo-sqlfluff/src/08_jinja.sql b/data/demo-sqlfluff/src/08_jinja.sql new file mode 100644 index 0000000..31369fd --- /dev/null +++ b/data/demo-sqlfluff/src/08_jinja.sql @@ -0,0 +1,6 @@ +-- Demo: jinja padding rule (JJ01) +-- {{1}} +SELECT + id +FROM + my_table; \ No newline at end of file diff --git a/data/demo-sqlfluff/src/09_aliasing_length.sql b/data/demo-sqlfluff/src/09_aliasing_length.sql new file mode 100644 index 0000000..2258fcd --- /dev/null +++ b/data/demo-sqlfluff/src/09_aliasing_length.sql @@ -0,0 +1,9 @@ +-- Demo: aliasing.length (AL06) +-- 补充样例:内置配置设置了 max_alias_length = 30,别名超过 30 字符即触发 +-- AL06(此前 demo 误认为该规则无法触发;min_alias_length 未设置时短别名 +-- 不报,但超长别名仍会被 max_alias_length 拦截)。 +SELECT + this_alias_is_way_longer_than_thirty_characters_ok.id +FROM + my_table this_alias_is_way_longer_than_thirty_characters_ok; +-- AL06: alias exceeds max_alias_length (30) diff --git a/data/demo-stylelint/reports/common-review.md b/data/demo-stylelint/reports/common-review.md new file mode 100644 index 0000000..bd27365 --- /dev/null +++ b/data/demo-stylelint/reports/common-review.md @@ -0,0 +1,472 @@ +# 代码审查报告 + +**文件:** `data\demo-stylelint\src\common.css` +**语言:** css +**耗时:** 218.1s +**分析工具:** stylelint + +--- + +总计: 150 | 错误: 145 | 警告: 4 | 建议: 1 + +静态分析 · 144 个问题 + +- 🔴 `stylelint:import-notation` L6 + 应使用字符串形式导入,而不是 url()。 + 建议: 将 @import url("/x.css") 改为 @import "/x.css";。 +- 🔴 `stylelint:color-named` L9 + 不允许使用命名颜色“red”。 + 建议: 改用十六进制或颜色函数,如 #f00。 +- 🔴 `stylelint:no-invalid-position-at-import-rule` L13 + @import 规则位置非法(位于其他规则之后)。 + 建议: 将所有 @import 规则移到其他样式规则之前。如果使用构建工具,应改用 link 标签或合并导入文件。 +- 🔴 `stylelint:at-rule-descriptor-no-unknown` L18 + @font-face 中使用了未知描述符“unknown-descriptor”。 + 建议: 删除 unknown-descriptor,或将其替换为合法的 @font-face 描述符,例如 font-family、src、font-weight。 +- 🔴 `stylelint:at-rule-descriptor-value-no-unknown` L23 + 字体描述符 font-display 的取值“invalid-display-value”非法。 + 建议: 使用合法的 font-display 值,如 auto、block、swap、fallback、optional。建议改为 font-display: swap;。 +- 🔴 `stylelint:at-rule-no-deprecated` L27 + 已废弃的 @ 规则“@viewport”。 + 建议: 不要再使用 @viewport,改用 viewport meta 标签或现代 CSS 视口单位方案;直接删除该规则。 +- 🔴 `stylelint:at-rule-no-unknown` L32 + 未知的 @ 规则“@unknown-rule”。 + 建议: 删除该未知规则,或改为有意义的规则,例如 @media 或 @supports。 +- 🔴 `stylelint:block-no-empty` L32 + 空规则块。 + 建议: 删除空块,或在其中添加实际样式声明。 +- 🔴 `stylelint:at-rule-prelude-no-invalid` L36 + @layer 规则的 prelude“foo.bar.”无效。 + 建议: 层名称不能以点结尾,应改为合法的层名称,如 foo.bar。 +- 🔴 `stylelint:block-no-empty` L36 + 空规则块。 + 建议: 删除空块,或在其中添加实际样式声明。 +- 🔴 `stylelint:at-rule-no-vendor-prefix` L40 + 使用了带厂商前缀的 @ 规则“@-webkit-keyframes”。 + 建议: 去掉厂商前缀,并使用标准 @keyframes;若需兼容旧浏览器,可在标准规则旁保留前缀版本。 +- 🔴 `stylelint:block-no-empty` L40 + 空规则块。 + 建议: 删除空块,或在其中添加实际样式声明。 +- 🔴 `stylelint:block-no-empty` L45 + 空规则块。 + 建议: 删除空块,或在其中添加实际样式声明。 +- 🔴 `stylelint:media-feature-name-no-unknown` L45 + 未知媒体特性“unknown-feature”。 + 建议: 使用有效的媒体特性,如 min-width、max-width、orientation。 +- 🔴 `stylelint:block-no-empty` L49 + 空规则块。 + 建议: 删除空块,或在其中添加实际样式声明。 +- 🔴 `stylelint:media-feature-name-value-no-unknown` L49 + 媒体特性 width 的值“10”未知。 + 建议: 媒体查询中的长度值需要单位,改为 10px。 +- 🔴 `stylelint:block-no-empty` L53 + 空规则块。 + 建议: 删除空块,或在其中添加实际样式声明。 +- 🔴 `stylelint:media-query-no-invalid` L53 + 无效的媒体查询“(min-width: )”。 + 建议: 补全媒体特性的值,如 min-width: 100px。 +- 🔴 `stylelint:block-no-empty` L57 + 空规则块。 + 建议: 删除空块,或在其中添加实际样式声明。 +- 🔴 `stylelint:media-type-no-deprecated` L57 + 已废弃的媒体类型“tv”。 + 建议: 改为 screen 或移除该媒体查询。 +- 🔴 `stylelint:block-no-empty` L61 + 空规则块。 + 建议: 删除空块,或在其中添加实际样式声明。 +- 🔴 `stylelint:media-feature-name-no-vendor-prefix` L61 + 带厂商前缀的媒体特性名。 + 建议: 使用标准 min-resolution 替代 -webkit-min-device-pixel-ratio。 +- 🔴 `stylelint:block-no-empty` L65 + 空规则块。 + 建议: 删除空块,或在其中添加实际样式声明。 +- 🔴 `stylelint:no-unknown-custom-media` L65 + 使用了未定义的自定义媒体查询“--my-custom-media”。 + 建议: 先用 @custom-media 定义,或改用标准媒体查询替代。 +- 🔴 `stylelint:no-duplicate-selectors` L70 + 重复的选择器“a”,首次使用在第 8 行。 + 建议: 合并相同选择器的规则,避免重复。 +- 🔴 `stylelint:annotation-no-unknown` L71 + 未知注解“!foo”。 + 建议: 删除 !foo,只保留 color: red。 +- 🔴 `stylelint:color-named` L71 + 不允许使用命名颜色“red”。 + 建议: 改用十六进制或颜色函数,如 #f00。 +- 🔴 `stylelint:declaration-property-value-no-unknown` L71 + 无法解析属性 color 的值“red !foo”。 + 建议: 删除 !foo,color 的值应为合法颜色。 +- 🔴 `stylelint:no-duplicate-selectors` L75 + 重复的选择器“a”,首次使用在第 8 行。 + 建议: 合并相同选择器的规则,避免重复。 +- 🔴 `stylelint:color-no-invalid-hex` L77 + 非法十六进制颜色“#12345”。 + 建议: 十六进制颜色必须是 3、4、6 或 8 位,改为 #123456 或 #123。 +- 🔴 `stylelint:declaration-block-no-duplicate-properties` L77 + 重复的属性“color”。 + 建议: 同一规则块中只能有一个 color 声明,删除多余声明。 +- 🔴 `stylelint:declaration-property-value-no-unknown` L77 + 属性 color 的值“#12345”未知。 + 建议: 使用合法的十六进制颜色,如 #123456。 +- 🔴 `stylelint:color-hex-length` L80 + 十六进制颜色过长,应缩短。 + 建议: 将 #ffffff 缩写为 #fff。 +- 🔴 `stylelint:color-function-notation` L83 + 应使用现代颜色函数记号。 + 建议: 使用空格分隔的现代语法,例如 rgb(255 0 0)。 +- 🔴 `stylelint:alpha-value-notation` L86 + 预计将“50%”改为“0.5”。 + 建议: alpha 通道值应使用 0~1 的数字,将 rgba(0, 0, 0, 50%) 改为 rgba(0, 0, 0, 0.5)。 +- 🔴 `stylelint:color-function-notation` L86 + 应使用现代颜色函数记号。 + 建议: 使用空格分隔的现代语法,例如 rgba(0 0 0 / 0.5)。 +- 🔴 `stylelint:color-function-notation` L89 + 应使用现代颜色函数记号。 + 建议: 使用空格分隔的现代语法,例如 hsl(120deg 50% 50%)。 +- 🔴 `stylelint:hue-degree-notation` L89 + 色相值“120”应使用角度表示“120deg”。 + 建议: 在 hsl/hwb 等颜色函数的色相值后添加 deg。 +- 🔴 `stylelint:no-duplicate-selectors` L93 + 重复的选择器“a”,首次使用在第 8 行。 + 建议: 合并相同选择器的规则,避免重复。 +- 🔴 `stylelint:color-named` L95 + 不允许使用命名颜色“red”。 + 建议: 改用十六进制或颜色函数,如 #f00。 +- 🔴 `stylelint:comment-no-empty` L100 + 空注释。 + 建议: 删除空注释,或在注释内添加说明文字。 +- 🔴 `stylelint:comment-whitespace-inside` L100 + 注释内“/*”后应有空格。 + 建议: 在注释内部加上空格,例如 /* comment */。 +- 🔴 `stylelint:comment-whitespace-inside` L100 + 注释内“*/”前应有空格。 + 建议: 在注释内部加上空格,例如 /* comment */。 +- 🔴 `stylelint:comment-whitespace-inside` L103 + 注释内“/*”后应有空格。 + 建议: 在注释内容两侧加空格,例如 /* comment */。 +- 🔴 `stylelint:comment-whitespace-inside` L103 + 注释内“*/”前应有空格。 + 建议: 在注释内容两侧加空格,例如 /* comment */。 +- 🔴 `stylelint:no-invalid-double-slash-comments` L106 + 非法的双斜杠 CSS 注释。 + 建议: CSS 不支持 // 注释,改为 /* */ 注释或删除该行。 +- 🔴 `stylelint:color-named` L109 + 不允许使用命名颜色“red”。 + 建议: 改用十六进制或颜色函数,如 #f00。 +- 🔴 `stylelint:color-named` L114 + 不允许使用命名颜色“red”。 + 建议: 改用十六进制或颜色函数,如 #f00。 +- 🔴 `stylelint:no-duplicate-selectors` L117 + 重复的选择器“a”,首次使用在第 8 行。 + 建议: 合并相同选择器的规则,避免重复。 +- 🔴 `stylelint:custom-property-no-missing-var-function` L119 + 自定义属性“--my-color”缺少 var() 函数。 + 建议: 使用 var() 包裹自定义属性,如 color: var(--my-color);。 +- 🔴 `stylelint:declaration-property-value-no-unknown` L119 + 属性 color 的值“--my-color”未知。 + 建议: 应该使用 var(--my-color) 取自定义属性值。 +- 🔴 `stylelint:declaration-block-no-duplicate-custom-properties` L123 + 重复的自定义属性“--dup”。 + 建议: 删除重复的自定义属性声明,只保留一个 --dup。 +- 🔴 `stylelint:no-unknown-custom-properties` L126 + 使用了未定义的自定义属性“--unknown-prop”。 + 建议: 先在根或作用域内定义 --unknown-prop,或使用其他已定义的自定义属性。 +- 🔴 `stylelint:import-notation` L131 + 应使用字符串形式导入,而不是 url()。 + 建议: 将 @import url("/a.css") 改为 @import "/a.css";。 +- 🔴 `stylelint:no-invalid-position-at-import-rule` L131 + @import 规则位置非法(位于其他规则之后)。 + 建议: 将所有 @import 规则移到其他样式规则之前。如果使用构建工具,应改用 link 标签或合并导入文件。 +- 🔴 `stylelint:import-notation` L132 + 应使用字符串形式导入,而不是 url()。 + 建议: 将 @import url("/a.css") 改为 @import "/a.css";。 +- 🔴 `stylelint:no-duplicate-at-import-rules` L132 + 重复的 @import 规则“/a.css”。 + 建议: 删除重复的 @import。 +- 🔴 `stylelint:no-invalid-position-at-import-rule` L132 + @import 规则位置非法(位于其他规则之后)。 + 建议: 将所有 @import 规则移到其他样式规则之前。如果使用构建工具,应改用 link 标签或合并导入文件。 +- 🔴 `stylelint:color-named` L136 + 不允许使用命名颜色“red”。 + 建议: 改用十六进制或颜色函数,如 #f00。 +- 🔴 `stylelint:no-descending-specificity` L138 + 低特异性选择器“.bar .foo”应放在高特异性选择器“#high .foo”之前。 + 建议: 将 .bar .foo 规则移到 #high .foo 规则之前,避免特异性顺序混乱。 +- 🔴 `stylelint:color-named` L139 + 不允许使用命名颜色“blue”。 + 建议: 改用十六进制或颜色函数,如 #00f。 +- 🔴 `stylelint:color-named` L144 + 不允许使用命名颜色“red”。 + 建议: 改用十六进制或颜色函数,如 #f00。 +- 🔴 `stylelint:no-duplicate-selectors` L146 + 重复的选择器“.dup”,首次使用在第 143 行。 + 建议: 合并两个 .dup 规则块。 +- 🔴 `stylelint:color-named` L147 + 不允许使用命名颜色“red”。 + 建议: 改用十六进制或颜色函数,如 #f00。 +- 🔴 `stylelint:color-named` L152 + 不允许使用命名颜色“red”。 + 建议: 改用十六进制或颜色函数,如 #f00。 +- 🔴 `stylelint:declaration-block-no-duplicate-properties` L152 + 重复的属性“color”。 + 建议: 删除其中的一个 color 声明,避免覆盖。 +- 🔴 `stylelint:color-named` L153 + 不允许使用命名颜色“blue”。 + 建议: 改用十六进制或颜色函数,如 #00f。 +- 🔴 `stylelint:color-named` L158 + 不允许使用命名颜色“blue”。 + 建议: 改用十六进制或颜色函数,如 #00f。 +- 🔴 `stylelint:color-named` L159 + 不允许使用命名颜色“red”。 + 建议: 改用十六进制或颜色函数,如 #f00。 +- 🔴 `stylelint:declaration-block-no-shorthand-property-overrides` L159 + 简写属性“background”覆盖了“background-color”。 + 建议: 避免被覆盖,删除 background-color 或调整顺序。 +- 🔴 `stylelint:no-duplicate-selectors` L163 + 重复的选择器“a”,首次使用在第 8 行。 + 建议: 合并相同选择器的规则,避免重复。 +- 🔴 `stylelint:font-family-no-duplicate-names` L165 + 字体族名“Arial”重复。 + 建议: 删除重复的 Arial,或换成其他字体族名。 +- 🔴 `stylelint:font-family-no-missing-generic-family-keyword` L165 + 缺少通用字体族关键字。 + 建议: 在字体族列表末尾添加通用族,如 sans-serif、serif、monospace。 +- 🔴 `stylelint:font-family-no-missing-generic-family-keyword` L168 + 缺少通用字体族关键字。 + 建议: 添加通用族,如 font-family: Arial, sans-serif;。 +- 🔴 `stylelint:declaration-property-value-no-unknown` L173 + 属性 font-family 的值“2Fancy”未知。 + 建议: 给字体名加引号,例如 font-family: "2Fancy";。 +- 🔴 `stylelint:font-family-name-quotes` L173 + 字体名“2Fancy”应加引号。 + 建议: 以数字开头的字体名必须用引号包裹:"2Fancy"。 +- 🔴 `stylelint:font-family-no-missing-generic-family-keyword` L173 + 缺少通用字体族关键字。 + 建议: 添加通用族,如 font-family: "2Fancy", sans-serif;。 +- 🔴 `stylelint:unit-no-unknown` L173 + 未知单位“Fancy”。 + 建议: 这实际上是字体名 2Fancy 被误解析为数字+单位;应该用引号包裹:"2Fancy"。 +- 🔴 `stylelint:no-duplicate-selectors` L177 + 重复的选择器“a”,首次使用在第 8 行。 + 建议: 合并相同选择器的规则,避免重复。 +- 🔴 `stylelint:declaration-property-value-no-unknown` L179 + 属性 color 的值“unknown-func()”未知。 + 建议: 使用合法的颜色函数,如 rgb()、hsl(),或定义自定义函数不是 CSS 标准。 +- 🔴 `stylelint:function-no-unknown` L179 + 未知函数“unknown-func”。 + 建议: 使用 CSS 标准函数替换,或移除该声明。 +- 🔴 `stylelint:function-calc-no-unspaced-operator` L182 + calc 表达式中的“+”运算符前应有空格。 + 建议: 在 + 两侧添加空格:calc(1px + 2px)。 +- 🔴 `stylelint:function-calc-no-unspaced-operator` L182 + calc 表达式中的“+”运算符后应有空格。 + 建议: 在 + 两侧添加空格:calc(1px + 2px)。 +- 🔴 `stylelint:color-named` L185 + 不允许使用命名颜色“red”。 + 建议: 改用十六进制或颜色函数,如 #f00。 +- 🔴 `stylelint:color-named` L185 + 不允许使用命名颜色“blue”。 + 建议: 改用十六进制或颜色函数,如 #00f。 +- 🔴 `stylelint:declaration-property-value-no-unknown` L185 + 属性 background 的值“linear-gradient(top, red, blue)”未知。 + 建议: 方向应使用 to bottom 语法,并避免命名颜色:linear-gradient(to bottom, #f00, #00f)。 +- 🔴 `stylelint:function-linear-gradient-no-nonstandard-direction` L185 + 线性渐变使用了非标准方向。 + 建议: 使用 to bottom 替代 top,表示从顶部到底部。 +- 🔴 `stylelint:no-unknown-animations` L188 + 未知动画“my-anim”。 + 建议: 为 my-anim 添加 @keyframes 定义,或修改 animation 名称。 +- 🔴 `stylelint:no-duplicate-selectors` L193 + 重复的选择器“a”,首次使用在第 8 行。 + 建议: 合并相同选择器的规则,避免重复。 +- 🔴 `stylelint:named-grid-areas-no-invalid` L196 + 命名网格区域每个字符串的单元格数量不一致。 + 建议: 调整网格区域字符串,使每行单元格数量相同。 +- 🔴 `stylelint:keyframe-selector-notation` L202 + 应使用百分比选择器“0%”替代“from”。 + 建议: 将 from 改为 0%。 +- 🔴 `stylelint:keyframe-block-no-duplicate-selectors` L205 + 关键帧块内重复的选择器“from”。 + 建议: 合并两个 from 块,保留一个。 +- 🔴 `stylelint:keyframe-selector-notation` L205 + 应使用百分比选择器“0%”替代“from”。 + 建议: 将 from 改为 0%。 +- 🔴 `stylelint:keyframe-selector-notation` L212 + 应使用百分比选择器“0%”替代“from”。 + 建议: 将 from 改为 0%。 +- 🔴 `stylelint:keyframe-declaration-no-important` L213 + 关键帧声明中不允许使用 !important。 + 建议: 删除 !important。 +- 🔴 `stylelint:keyframe-selector-notation` L219 + 应使用百分比选择器“0%”替代“from”。 + 建议: 将 from 改为 0%。 +- 🔴 `stylelint:no-duplicate-selectors` L225 + 重复的选择器“a”,首次使用在第 8 行。 + 建议: 合并相同选择器的规则,避免重复。 +- 🔴 `stylelint:length-zero-no-unit` L227 + 长度为 0 时不应带单位。 + 建议: 将 0px 改为 0。 +- 🔴 `stylelint:number-max-precision` L230 + 数值精度超过 4 位,期望“1.1235”。 + 建议: 将 1.123456 四舍五入为 1.1235。 +- 🔴 `stylelint:no-duplicate-selectors` L233 + 重复的选择器“b”,首次使用在第 171 行。 + 建议: 合并相同选择器的规则,避免重复。 +- 🔴 `stylelint:declaration-property-value-no-unknown` L235 + 属性 width 的值“10unknown”未知。 + 建议: 10unknown 不是合法长度,请改为 10px、10rem 等有效值。 +- 🔴 `stylelint:unit-no-unknown` L235 + 未知单位“unknown”。 + 建议: 10unknown 不是合法长度,改为 10px 或 10rem 等。 +- 🔴 `stylelint:no-duplicate-selectors` L239 + 重复的选择器“a”,首次使用在第 8 行。 + 建议: 合并相同选择器的规则,避免重复。 +- 🔴 `stylelint:color-named` L240 + 不允许使用命名颜色“red”。 + 建议: 改用十六进制或颜色函数,如 #f00。 +- 🔴 `stylelint:block-no-redundant-nested-style-rules` L242 + 冗余嵌套样式规则。 + 建议: 删除无意义的嵌套 & 块,因为外层 a 已经设置了相同样式。 +- 🔴 `stylelint:no-duplicate-selectors` L242 + 重复的选择器“&”,首次使用在第 8 行。 + 建议: 嵌套选择器 & 不能视为独立选择器,应删除冗余嵌套块。 +- 🔴 `stylelint:color-named` L243 + 不允许使用命名颜色“red”。 + 建议: 改用十六进制或颜色函数,如 #f00。 +- 🔴 `stylelint:nesting-selector-no-missing-scoping-root` L249 + 缺少嵌套作用域根,顶层出现了嵌套选择器“&”。 + 建议: 将 &:hover 放入某个选择器内部,或改为普通选择器 a:hover。 +- 🔴 `stylelint:color-named` L250 + 不允许使用命名颜色“red”。 + 建议: 改用十六进制或颜色函数,如 #f00。 +- 🔴 `stylelint:color-named` L255 + 不允许使用命名颜色“red”。 + 建议: 改用十六进制或颜色函数,如 #f00。 +- 🔴 `stylelint:no-invalid-position-declaration` L255 + 声明位置非法(位于 @media 块内但没有选择器)。 + 建议: 将 color: red; 放入一个选择器内部,或删除该声明。 +- 🔴 `stylelint:no-duplicate-selectors` L259 + 重复的选择器“a”,首次使用在第 8 行。 + 建议: 合并相同选择器的规则,避免重复。 +- 🔴 `stylelint:color-named` L261 + 不允许使用命名颜色“red”。 + 建议: 改用十六进制或颜色函数,如 #f00。 +- 🔴 `stylelint:property-no-unknown` L261 + 未知属性“colour”。 + 建议: CSS 中没有 colour,应改为 color。 +- 🔴 `stylelint:property-no-deprecated` L264 + 已废弃的属性“word-wrap”。 + 建议: 改用标准属性 overflow-wrap。 +- 🔴 `stylelint:property-no-vendor-prefix` L267 + 带厂商前缀的属性“-webkit-transform”。 + 建议: 使用标准 transform 属性,无需前缀。 +- 🔴 `stylelint:declaration-property-value-no-unknown` L270 + 属性 width 的值“unknownvalue”未知。 + 建议: width 不接受未定义关键字,请改用 auto、百分比、长度等。 +- 🔴 `stylelint:no-duplicate-selectors` L273 + 重复的选择器“b”,首次使用在第 171 行。 + 建议: 合并相同选择器的规则,避免重复。 +- 🔴 `stylelint:declaration-property-value-keyword-no-deprecated` L275 + 废弃的颜色关键字“activeborder”应替换为“ButtonBorder”。 + 建议: 使用系统颜色关键字 ButtonBorder,替换 activeborder。 +- 🔴 `stylelint:no-duplicate-selectors` L279 + 重复的选择器“a”,首次使用在第 8 行。 + 建议: 合并相同选择器的规则,避免重复。 +- 🔴 `stylelint:declaration-block-no-redundant-longhand-properties` L282 + 冗余的长手写属性“margin-top”位于简写属性“margin”之后。 + 建议: 删除冗余的 margin-top,因为 margin: 1px; 已设置所有边距。 +- 🔴 `stylelint:no-duplicate-selectors` L285 + 重复的选择器“b”,首次使用在第 171 行。 + 建议: 合并相同选择器的规则,避免重复。 +- 🔴 `stylelint:shorthand-property-no-redundant-values` L287 + 简写值冗余,期望“1px”。 + 建议: margin: 1px 1px 1px 1px 可以简写为 margin: 1px;。 +- 🔴 `stylelint:block-no-empty` L292 + 空规则块。 + 建议: 删除空块,或在其中添加实际样式声明。 +- 🔴 `stylelint:selector-pseudo-class-no-unknown` L292 + 未知伪类选择器“:unknown-pseudo”。 + 建议: 使用合法的伪类,如 :hover、:focus、:first-child,或删除该规则。 +- 🔴 `stylelint:block-no-empty` L296 + 空规则块。 + 建议: 删除空块,或在其中添加实际样式声明。 +- 🔴 `stylelint:selector-pseudo-element-no-unknown` L296 + 未知伪元素“::unknown-element”。 + 建议: 使用合法的伪元素,如 ::before、::after、::placeholder,或删除该规则。 +- 🔴 `stylelint:block-no-empty` L300 + 空规则块。 + 建议: 删除空块,或在其中添加实际样式声明。 +- 🔴 `stylelint:selector-pseudo-element-colon-notation` L300 + 伪元素应使用双冒号写法。 + 建议: 将 :before 改为 ::before。 +- 🔴 `stylelint:block-no-empty` L304 + 空规则块。 + 建议: 删除空块,或在其中添加实际样式声明。 +- 🔴 `stylelint:selector-type-no-unknown` L304 + 未知元素类型“unknowntag”。 + 建议: 使用合法的 HTML 标签,或为自定义元素添加连字符(如 unknown-tag)并确保已在 HTML 中注册。 +- 🔴 `stylelint:block-no-empty` L308 + 空规则块。 + 建议: 删除空块,或在其中添加实际样式声明。 +- 🔴 `stylelint:selector-no-vendor-prefix` L308 + 带厂商前缀的选择器“::-webkit-input-placeholder”。 + 建议: 使用标准伪元素 ::placeholder,并最好为旧浏览器保留前缀版本(如同时提供 ::-webkit-input-placeholder 和 ::placeholder)。 +- 🔴 `stylelint:block-no-empty` L312 + 空规则块。 + 建议: 删除空块,或在其中添加实际样式声明。 +- 🔴 `stylelint:selector-anb-no-unmatchable` L312 + 无法匹配任何元素的 An+B 选择器“:nth-child(0n)”。 + 建议: 删除该规则,或改用能匹配元素的表达式,如 nth-child(n)。 +- 🔴 `stylelint:block-no-empty` L316 + 空规则块。 + 建议: 删除空块,或在其中添加实际样式声明。 +- 🔴 `stylelint:string-no-newline` L321 + 字符串包含换行。 + 建议: 属性选择器中的字符串不能跨行,应使用转义(如 \a)或调整属性值。 +- 🔴 `stylelint:color-named` L323 + 不允许使用命名颜色“red”。 + 建议: 改用十六进制或颜色函数,如 #f00。 +- 🔴 `stylelint:syntax-string-no-invalid` L328 + 非法的语法字符串“”。 + 建议: @property 的 syntax 必须使用合法的 CSS 语法关键字,如 。 +- 🔴 `stylelint:no-duplicate-selectors` L333 + 重复的选择器“a”,首次使用在第 8 行。 + 建议: 合并相同选择器的规则,避免重复。 +- 🔴 `stylelint:value-no-vendor-prefix` L335 + 带厂商前缀的值“-webkit-flex”。 + 建议: 使用标准值 flex。 +- 🔴 `stylelint:no-duplicate-selectors` L340 + 重复的选择器“a”,首次使用在第 8 行。 + 建议: 合并相同选择器的规则,避免重复。 +- 🔴 `stylelint:declaration-property-value-no-unknown` L341 + 属性 margin 的值“0 ”未知(包含不间断空格)。 + 建议: 删除 margin 值中的不可见空格,改为 margin: 0;。 +- 🔴 `stylelint:no-irregular-whitespace` L341 + 发现不规则空白字符(通常为不换行空格)。 + 建议: 删除 0 后面的不换行空格,保留普通空格。 + +AI 审查 · 6 条建议 + +- 🔴 [AI] [bug] `font-face-required-descriptors` L17 + **@font-face 缺少必需的 font-family 和 src 描述符** + 文件中的两个 @font-face 规则都没有定义 font-family 和 src。根据 CSS Fonts 规范,@font-face 必须包含这两个描述符,否则整个规则无效,浏览器不会加载任何自定义字体。当前示例中还包含未知描述符/非法值,更加重了问题。 + 建议: 为每个 @font-face 补齐 font-family(自定义字体名称)和 src(字体资源路径与格式),例如:@font-face { font-family: "MyFont"; src: url("/fonts/myfont.woff2") format("woff2"); font-display: swap; } +- 🟡 [AI] [performance] `uncompressed-merged-output` L2 + **声称是合并压缩文件,但实际未压缩,存在大量冗余** + 文件注释说明“由 22 个 CSS 文件合并压缩而来”,但内容保留了所有源文件注释、空行、重复选择器和重复声明。这种“压缩”并未减小体积,仍会产生大量冗余下载流量,且重复规则会增加层叠冲突和样式表解析时间。 + 建议: 使用 CSS 压缩工具(如 cssnano、clean-css、csso)对合并产物进行压缩,并在构建流程中剥离开发注释、合并重复选择器。 +- 🟡 [AI] [performance] `import-performance` L6 + **@import 引入额外样式会阻塞渲染,应避免** + 文件中存在多个 @import 规则(包括第 6、13、131、132 行),即使将位置移到顶部,@import 也会使浏览器必须串行下载依赖样式,延迟页面渲染。更严重的是一部分 @import 位于非法位置,会被浏览器忽略,导致依赖样式缺失。 + 建议: 改用 在 HTML 中引用样式,或在构建阶段将 @import 的文件直接合并进当前文件。 +- 🟡 [AI] [bug] `dead-color-declarations` L89 + **同一规则块内多条 color 声明相互覆盖,形成死代码** + 从第 75 行开始的 a 规则块中依次声明了 #12345、#ffffff、rgb(255, 0, 0)、rgba(0, 0, 0, 50%) 和 hsl(120, 50%, 50%),但同一属性在同一个块中只有最后一个会生效,前面的颜色值全部被覆盖,属于无效代码。这也导致该块变得难以理解。 + 建议: 删除被覆盖的 color 声明,只保留最终需要生效的 hsl(120deg 50% 50%)(并根据 color-function-notation 的规则写为现代空格式)。 +- 🟡 [AI] [design] `missing-standard-prefix-fallback` L267 + **仅使用带厂商前缀的语法,缺少标准无前缀写法** + 代码中只出现了 -webkit-transform、-webkit-keyframes、-webkit-min-device-pixel-ratio、-webkit-flex、::-webkit-input-placeholder 等带前缀的写法,但没有对应的标准写法。现代浏览器可能不再支持旧前缀语法,导致这些样式在主流环境中失效;同时也忽略了其他需要前缀的浏览器。 + 建议: 同时提供标准属性和前缀属性(通常标准写法靠后),例如 transform 和 -webkit-transform 都写,或者直接使用标准属性并配合 Autoprefixer 等工具自动添加前缀。 +- 🔵 [AI] [design] `missing-charset-declaration` L1 + **文件包含非 ASCII 字符但未声明 @charset** + 该 CSS 文件中包含大量中文注释,但在文件开头没有 @charset "UTF-8"; 声明。如果通过 file:// 协议打开或 HTTP 头未指定编码,中文注释可能被错误解码,导致注释边界解析错误。 + 建议: 在文件第一行添加 @charset "UTF-8";,并确保文件保存为 UTF-8 编码。 diff --git a/data/demo-stylelint/reports/demo-stylelint插件实测覆盖率报告.md b/data/demo-stylelint/reports/demo-stylelint插件实测覆盖率报告.md new file mode 100644 index 0000000..5efa09c --- /dev/null +++ b/data/demo-stylelint/reports/demo-stylelint插件实测覆盖率报告.md @@ -0,0 +1,129 @@ +# demo-stylelint 插件实测覆盖率报告 + +> **工程**:`demo-stylelint`(`common.css` / `empty-source.css` 两个样例文件,无 `.stylelintrc` 工程配置 → 插件回退内置配置) +> **测试对象**:插件 Stylelint 适配器(引擎 stylelint 17.14.0 + stylelint-config-recommended 18.0.0) +> **实测日期**:2026-08-25 +> **比对基线**:与插件依赖版本逐字一致的本地复现(比对键 = 规则 + 行号 + 严重级别) + +--- + +## 一、核心结论 + +| 指标 | 结果 | 状态 | +|---|---|---| +| 规则覆盖率(内置启用 68 条) | **68/68 种全部触发(100%)** | ✅ 全覆盖 | +| 插件行为一致性(vs 基线) | **145/145 条逐条一致,零偏差** | ✅ 100% | +| 诊断总数 | **145 处**(common.css 144 + empty-source.css 1) | ✅ 与基线完全一致 | +| 缺陷发现 | **无**(无 NaN 行号、无解析错误、无规则丢失) | ✅ | + +**实测完全达成全覆盖基准**:插件在「工程无配置文件 → 回退内置配置」路径下,内置启用的 68 条规则全部触发,145 处诊断与模拟基线逐条零偏差。**Stylelint 适配器静态分析链路一次通过完整验证,未发现任何缺陷。** + +--- + +## 二、逐文件比对(实测 vs 基线) + +| 文件 | 实测 | 基线 | 一致性 | 触发规则数 | +|---|---:|---:|---|---| +| `common.css` | 144 | 144 | ✅ 逐条一致 | 67 种 | +| `empty-source.css` | 1 | 1 | ✅ 逐条一致 | 1 种(`no-empty-source`,仅此文件触发) | +| **合计** | **145** | **145** | **✅ 零偏差** | **68 种(67 + 1 独有)** | + +> 比对方法:将插件报告中每条静态诊断(规则名 + 行号 + 严重级别)与基线做多重集合比对;两个文件均 `missing = 0`、`extra = 0`。 + +--- + +## 三、规则覆盖拆解(68/68 = 100%) + +内置配置 = `stylelint-config-recommended` 18.0.0 原生规则 **41 条** + 插件追加 `stylelintExtraRules` **27 条**(无重叠,合并 68 条),两部分实测**全部触发**: + +| 来源 | 启用 | 触发 | 说明 | +|---|---:|---:|---| +| recommended 18.0.0 原生 | 41 | **41/41** | 含 `at-rule-descriptor-no-unknown`、`declaration-property-value-no-unknown`、`no-duplicate-selectors`、`selector-pseudo-class-no-unknown` 等核心规则 | +| 插件 extra 追加 | 27 | **27/27** | 校验类 7 条(`color-no-invalid-hex`、`function-no-unknown`、`unit-no-unknown` 等)+ 厂商前缀类 5 条 + 记号规范类 8 条 + 其他 7 条 | +| **合计** | **68** | **68(100%)** | — | + +**extra 追加 27 条明细**(全部触发): + +- **校验类(7)**:`color-no-invalid-hex`、`function-linear-gradient-no-nonstandard-direction`、`function-no-unknown`、`unit-no-unknown`、`no-unknown-animations`、`no-unknown-custom-media`、`no-unknown-custom-properties` +- **厂商前缀类(5)**:`at-rule-no-vendor-prefix`、`media-feature-name-no-vendor-prefix`、`property-no-vendor-prefix`、`selector-no-vendor-prefix`、`value-no-vendor-prefix` +- **记号规范类(8)**:`color-hex-length`、`color-function-notation`、`length-zero-no-unit`、`selector-pseudo-element-colon-notation`、`import-notation`、`alpha-value-notation`、`hue-degree-notation`、`keyframe-selector-notation` +- **其他(7)**:`declaration-block-no-redundant-longhand-properties`、`shorthand-property-no-redundant-values`、`block-no-redundant-nested-style-rules`、`color-named`、`font-family-name-quotes`、`number-max-precision`、`comment-whitespace-inside` + +--- + +## 四、诊断分布统计 + +**按严重级别**(静态 145 条): + +| 级别 | 数量 | 占比 | +|---|---:|---:| +| error(🔴) | 145 | 100% | +| warning(🟡) | 0 | 0% | + +**按文件**:`common.css` 144 · `empty-source.css` 1。 + +**高频规则 Top 8**(common.css): + +| 规则 | 出现次数 | +|---|---:| +| `color-named`(不允许命名颜色) | 21 | +| `no-duplicate-selectors`(重复选择器) | 18 | +| `block-no-empty`(空规则块) | 16 | +| `declaration-property-value-no-unknown`(未知属性值) | 9 | +| `comment-whitespace-inside`(注释内空格) | 4 | +| `keyframe-selector-notation`(关键帧选择器记号) | 4 | +| `import-notation`(导入记号) | 3 | +| `no-invalid-position-at-import-rule`(@import 位置非法) | 3 | + +> 注:插件报告中另有 AI 审查建议 6 条(common.css:bug 2 / performance 2 / design 2),属 AI 增强能力,不计入静态规则覆盖口径。 + +--- + +## 五、实测环境判定 + +实测报告的文件路径与预期部署一致: + +- `data\demo-stylelint\src\common.css`(144 条诊断,行号与基线逐条吻合 → 文件与当前工作区版本一致) +- `data\demo-stylelint\src\empty-source.css`(1 条 `no-empty-source`,覆盖该规则在 recommended 中的唯一触发途径——空源文件) + +**配置路径验证**:工程不含任何 `.stylelintrc` / `stylelint.config.*`,插件按设计走「内置默认配置回退」路径(`recommended.rules` + `stylelintExtraRules` 直接作为 `config` 对象传入 `stylelint.lint()`)。实测 145/145 逐条一致即为该路径的最终确认。 + +**与 SQLFluff JJ01 缺陷的对照**:Stylelint 适配器对 warnings 的 `endLine`/`endColumn` 做了缺失回退(`w.endLine ?? w.line`),因此即使个别警告缺失结束坐标也不会出现 NaN 行号——实测无任何行号异常,该防御性写法经受住了 145 条诊断的验证。 + +--- + +## 六、数据口径说明 + +| 项 | 说明 | +|---|---| +| 覆盖率口径 | 68 条 = 内置配置实际启用规则数(recommended 41 + extra 27,无重叠);规则触发即计入 | +| 比对引擎 | stylelint 17.14.0 + stylelint-config-recommended 18.0.0(与插件 `package-lock.json` 锁定版本逐字一致) | +| 基线复现方式 | 与适配器 `getDefaultConfig()` 逻辑逐字一致:`import('stylelint-config-recommended')` 取 rules 后合并 `stylelintExtraRules`,作为 `config` 传入 `stylelint.lint({ code, codeFilename, cwd })` | +| 基线数据 | `/data/user/work/results/stylelint-final-compare.json`(逐文件比对明细) | +| 严重级别口径 | 145 条全部 error:recommended 与 extra 均未配置 `severity: warning` 覆盖,stylelint 默认全部 error | + +--- + +## 七、结论与后续 + +**demo-stylelint 工程的插件实测验证完成**: + +1. ✅ 内置规则全覆盖达成(68/68 种 · 145 处) +2. ✅ 插件行为零偏差(145/145 条逐条一致) +3. ✅ 「内置配置回退」路径完整验证(工程无 `.stylelintrc` 场景) +4. ✅ 零缺陷(对照 SQLFlutt 发现的 JJ01 NaN 问题,Stylelint 适配器的 endLine 回退写法正确) + +**实测进度总览**(四个 demo 工程): + +| 工程 | 实测状态 | 结果 | +|---|---|---| +| demo-eslint | ✅ 完成 | 126/126 种 · 292/292 条逐条一致 | +| demo-sqlfluff | ✅ 完成 | 57/57 种 · 113 处(112 条零偏差 + JJ01 行号显示缺陷) | +| demo-stylelint | ✅ 完成 | **68/68 种 · 145/145 条逐条零偏差** | +| demo-pmd | ⏳ 待实测 | 预期 281 条规则(Java 269 + JSP 12),注意 Java 版本与 ruleset | + +**后续建议**:仅剩 `demo-pmd` 待实测——测试机需 JDK 8+(插件用 PmdRunner.class 直跑,无沙箱 JDK 11 限制),打开 Java/JSP 样例文件后与预期基线比对即可,全套四工程验证收官。 + +--- + +*报告生成于 2026-08-25 · demo-stylelint 插件实测 · 比对脚本:`stylelint-compare.mjs` · 数据:`stylelint-final-compare.json`* diff --git a/data/demo-stylelint/reports/empty-source-review.md b/data/demo-stylelint/reports/empty-source-review.md new file mode 100644 index 0000000..5cef33d --- /dev/null +++ b/data/demo-stylelint/reports/empty-source-review.md @@ -0,0 +1,16 @@ +# 代码审查报告 + +**文件:** `data\demo-stylelint\src\empty-source.css` +**语言:** css +**耗时:** 10.6s +**分析工具:** stylelint + +--- + +总计: 1 | 错误: 1 | 警告: 0 | 建议: 0 + +静态分析 · 1 个问题 + +- 🔴 `stylelint:no-empty-source` L1 + 源文件为空:未包含任何有效内容。 + 建议: 删除此空文件,或添加至少一条 CSS(或所用预处理器)样式规则,使其不再为空。 diff --git a/data/demo-stylelint/src/common.css b/data/demo-stylelint/src/common.css new file mode 100644 index 0000000..7fc3ee1 --- /dev/null +++ b/data/demo-stylelint/src/common.css @@ -0,0 +1,342 @@ +/* ============ common.css(合并文件) ============ */ +/* 由 Stylelint 演示工程除 empty-source.css 外的 22 个 CSS 文件合并压缩而来。 */ + +/* ===== 原 import.css ===== */ +/* import-notation: 应使用字符串形式 */ +@import url("/x.css"); + +a { + color: red; +} + +/* no-invalid-position-at-import-rule: @import 位置非法(位于其他规则之后) */ +@import "/late.css"; + +/* ===== 原 at-rules.css ===== */ +/* at-rule-descriptor-no-unknown: @font-face 内未知描述符 */ +@font-face { + unknown-descriptor: 1; +} + +/* at-rule-descriptor-value-no-unknown: font-display 描述符值非法 */ +@font-face { + font-display: invalid-display-value; +} + +/* at-rule-no-deprecated: 已废弃的 at-rule */ +@viewport { + width: 100vh; +} + +/* at-rule-no-unknown: 未知 at-rule */ +@unknown-rule { +} + +/* at-rule-prelude-no-invalid: 无效的 prelude */ +@layer foo.bar. { +} + +/* at-rule-no-vendor-prefix: 带厂商前缀的 at-rule */ +@-webkit-keyframes vendor-keyframes { +} + +/* ===== 原 media.css ===== */ +/* media-feature-name-no-unknown: 未知媒体特性 */ +@media (unknown-feature) { +} + +/* media-feature-name-value-no-unknown: 媒体特性值非法 */ +@media (width: 10) { +} + +/* media-query-no-invalid: 无效 media 查询 */ +@media (min-width: ) { +} + +/* media-type-no-deprecated: 已废弃媒体类型 */ +@media tv { +} + +/* media-feature-name-no-vendor-prefix: 带厂商前缀的媒体特性 */ +@media (-webkit-min-device-pixel-ratio: 2) { +} + +/* no-unknown-custom-media: 引用未定义的自定义媒体 */ +@media (--my-custom-media) { +} + +/* ===== 原 annotation.css ===== */ +/* annotation-no-unknown: 未知的注解 "!foo" */ +a { + color: red !foo; +} + +/* ===== 原 color.css ===== */ +a { + /* color-no-invalid-hex: 非法十六进制颜色(5 位) */ + color: #12345; + + /* color-hex-length: 十六进制过长(应缩短为 3 位) */ + color: #ffffff; + + /* color-function-notation: 应使用现代空格分隔语法 */ + color: rgb(255, 0, 0); + + /* alpha-value-notation: alpha 应使用数字而非百分比 */ + color: rgba(0, 0, 0, 50%); + + /* hue-degree-notation: 色相应使用角度 */ + color: hsl(120, 50%, 50%); +} + +/* ===== 原 color-named.css ===== */ +a { + /* color-named: 禁止使用命名颜色 */ + color: red; +} + +/* ===== 原 comments.css ===== */ +/* comment-no-empty: 空注释 */ +/**/ + +/* comment-whitespace-inside: 注释内部缺少空格 */ +/*comment*/ + +/* no-invalid-double-slash-comments: 非法双斜杠注释 */ +// invalid double slash comment + +a { + color: red; +} + +/* ===== 原 custom-properties.css ===== */ +:root { + --my-color: red; +} + +a { + /* custom-property-no-missing-var-function: 自定义属性未包裹 var() */ + color: --my-color; + + /* declaration-block-no-duplicate-custom-properties: 重复自定义属性 */ + --dup: 1; + --dup: 2; + + /* no-unknown-custom-properties: 使用未定义的自定义属性 */ + background: var(--unknown-prop); +} + +/* ===== 原 duplicates.css ===== */ +/* no-duplicate-at-import-rules: 重复导入同一文件 */ +@import url("/a.css"); +@import url("/a.css"); + +/* no-descending-specificity: 低特异性选择器位于高特异性之后 */ +#high .foo { + color: red; +} +.bar .foo { + color: blue; +} + +/* no-duplicate-selectors: 重复选择器 */ +.dup { + color: red; +} +.dup { + color: red; +} + +/* declaration-block-no-duplicate-properties: 重复属性 */ +.dup2 { + color: red; + color: blue; +} + +/* declaration-block-no-shorthand-property-overrides: 简写属性被覆盖 */ +.dup3 { + background-color: blue; + background: red; +} + +/* ===== 原 font-family.css ===== */ +a { + /* font-family-no-duplicate-names: 重复字族名 */ + font-family: Arial, Arial; + + /* font-family-no-missing-generic-family-keyword: 缺少通用字族 */ + font-family: Arial; +} + +b { + /* font-family-name-quotes: 需加引号的字族名 */ + font-family: 2Fancy; +} + +/* ===== 原 functions.css ===== */ +a { + /* function-no-unknown: 未知函数 */ + color: unknown-func(); + + /* function-calc-no-unspaced-operator: calc 运算符缺少空格 */ + width: calc(1px+2px); + + /* function-linear-gradient-no-nonstandard-direction: 非标准方向 */ + background: linear-gradient(top, red, blue); + + /* no-unknown-animations: 引用未定义的动画 */ + animation: my-anim 1s; +} + +/* ===== 原 grids.css ===== */ +/* named-grid-areas-no-invalid: 命名网格区域行列数量不一致 */ +a { + grid-template-areas: + "a b" + "c d e"; +} + +/* ===== 原 keyframes.css ===== */ +/* keyframe-block-no-duplicate-selectors: 关键帧块内重复选择器 */ +@keyframes duplicate-selector { + from { + opacity: 0; + } + from { + opacity: 0; + } +} + +/* keyframe-declaration-no-important: 关键帧声明使用 !important */ +@keyframes important { + from { + opacity: 0 !important; + } +} + +/* keyframe-selector-notation: 应使用百分比选择器 */ +@keyframes notation { + from { + opacity: 0; + } +} + +/* ===== 原 length.css ===== */ +a { + /* length-zero-no-unit: 零值不应带单位 */ + margin: 0px; + + /* number-max-precision: 数值精度超过 4 位 */ + width: 1.123456px; +} + +b { + /* unit-no-unknown: 未知单位 */ + width: 10unknown; +} + +/* ===== 原 nested-redundant.css ===== */ +a { + color: red; + /* block-no-redundant-nested-style-rules: 冗余嵌套样式规则 */ + & { + color: red; + } +} + +/* ===== 原 nesting.css ===== */ +/* nesting-selector-no-missing-scoping-root: 顶层出现嵌套选择器 */ +&:hover { + color: red; +} + +/* no-invalid-position-declaration: 声明处于非法位置(media 块内无选择器) */ +@media (min-width: 100px) { + color: red; +} + +/* ===== 原 properties.css ===== */ +a { + /* property-no-unknown: 未知属性 */ + colour: red; + + /* property-no-deprecated: 已废弃属性 */ + word-wrap: break-word; + + /* property-no-vendor-prefix: 带厂商前缀的属性 */ + -webkit-transform: rotate(1deg); + + /* declaration-property-value-no-unknown: 属性值未知 */ + width: unknownvalue; +} + +b { + /* declaration-property-value-keyword-no-deprecated: 已废弃颜色关键字 */ + color: activeborder; +} + +/* ===== 原 redundancy.css ===== */ +a { + /* declaration-block-no-redundant-longhand-properties: 冗余长手写属性 */ + margin: 1px; + margin-top: 1px; +} + +b { + /* shorthand-property-no-redundant-values: 冗余简写值 */ + margin: 1px 1px 1px 1px; +} + +/* ===== 原 selectors.css ===== */ +/* selector-pseudo-class-no-unknown: 未知伪类 */ +a:unknown-pseudo { +} + +/* selector-pseudo-element-no-unknown: 未知伪元素 */ +a::unknown-element { +} + +/* selector-pseudo-element-colon-notation: 伪元素应使用双冒号 */ +a:before { +} + +/* selector-type-no-unknown: 未知元素类型 */ +unknowntag { +} + +/* selector-no-vendor-prefix: 带厂商前缀的伪元素选择器 */ +::-webkit-input-placeholder { +} + +/* selector-anb-no-unmatchable: 无法匹配的 An+B 选择器 */ +li:nth-child(0n) { +} + +/* block-no-empty: 空规则块 */ +div { +} + +/* ===== 原 strings.css ===== */ +/* string-no-newline: 字符串包含换行 */ +[data-x="line +break"] { + color: red; +} + +@property --foo { + /* syntax-string-no-invalid: 非法语法字符串 */ + syntax: ""; + inherits: false; +} + +/* ===== 原 vendor-value.css ===== */ +a { + /* value-no-vendor-prefix: 带厂商前缀的值 */ + display: -webkit-flex; +} + +/* ===== 原 whitespace.css ===== */ +/* no-irregular-whitespace: value contains NBSP */ +a { + margin: 0 ; +} diff --git a/data/demo-stylelint/src/empty-source.css b/data/demo-stylelint/src/empty-source.css new file mode 100644 index 0000000..e69de29 diff --git a/data/demo-stylelint/stylelint_line_map.json b/data/demo-stylelint/stylelint_line_map.json new file mode 100644 index 0000000..f0db751 --- /dev/null +++ b/data/demo-stylelint/stylelint_line_map.json @@ -0,0 +1,1448 @@ +{ + "target_rules_count": 68, + "hit_count": 55, + "target_rules": [ + "alpha-value-notation", + "annotation-no-unknown", + "at-rule-descriptor-no-unknown", + "at-rule-descriptor-value-no-unknown", + "at-rule-no-deprecated", + "at-rule-no-unknown", + "at-rule-no-vendor-prefix", + "at-rule-prelude-no-invalid", + "block-no-empty", + "block-no-redundant-nested-style-rules", + "color-function-notation", + "color-hex-length", + "color-named", + "color-no-invalid-hex", + "comment-no-empty", + "comment-whitespace-inside", + "custom-property-no-missing-var-function", + "declaration-block-no-duplicate-custom-properties", + "declaration-block-no-duplicate-properties", + "declaration-block-no-redundant-longhand-properties", + "declaration-block-no-shorthand-property-overrides", + "declaration-property-value-keyword-no-deprecated", + "declaration-property-value-no-unknown", + "font-family-name-quotes", + "font-family-no-duplicate-names", + "font-family-no-missing-generic-family-keyword", + "function-calc-no-unspaced-operator", + "function-linear-gradient-no-nonstandard-direction", + "function-no-unknown", + "hue-degree-notation", + "import-notation", + "keyframe-block-no-duplicate-selectors", + "keyframe-declaration-no-important", + "keyframe-selector-notation", + "length-zero-no-unit", + "media-feature-name-no-unknown", + "media-feature-name-no-vendor-prefix", + "media-feature-name-value-no-unknown", + "media-query-no-invalid", + "media-type-no-deprecated", + "named-grid-areas-no-invalid", + "nesting-selector-no-missing-scoping-root", + "no-descending-specificity", + "no-duplicate-at-import-rules", + "no-duplicate-selectors", + "no-empty-source", + "no-invalid-double-slash-comments", + "no-invalid-position-at-import-rule", + "no-invalid-position-declaration", + "no-irregular-whitespace", + "no-unknown-animations", + "no-unknown-custom-media", + "no-unknown-custom-properties", + "number-max-precision", + "property-no-deprecated", + "property-no-unknown", + "property-no-vendor-prefix", + "selector-anb-no-unmatchable", + "selector-no-vendor-prefix", + "selector-pseudo-class-no-unknown", + "selector-pseudo-element-colon-notation", + "selector-pseudo-element-no-unknown", + "selector-type-no-unknown", + "shorthand-property-no-redundant-values", + "string-no-newline", + "syntax-string-no-invalid", + "unit-no-unknown", + "value-no-vendor-prefix" + ], + "missing": [ + "at-rule-descriptor-no-unknown", + "at-rule-descriptor-value-no-unknown", + "at-rule-no-deprecated", + "at-rule-prelude-no-invalid", + "declaration-block-no-redundant-longhand-properties", + "declaration-property-value-keyword-no-deprecated", + "declaration-property-value-no-unknown", + "media-feature-name-value-no-unknown", + "media-type-no-deprecated", + "nesting-selector-no-missing-scoping-root", + "no-invalid-position-declaration", + "property-no-deprecated", + "syntax-string-no-invalid" + ], + "triggered_extra": [], + "file_rule_map": { + "common.css": [ + { + "rule": "alpha-value-notation", + "line": 86, + "col": 24 + }, + { + "rule": "annotation-no-unknown", + "line": 71, + "col": 14 + }, + { + "rule": "at-rule-no-unknown", + "line": 32, + "col": 1 + }, + { + "rule": "at-rule-no-vendor-prefix", + "line": 40, + "col": 1 + }, + { + "rule": "block-no-empty", + "line": 292, + "col": 18 + }, + { + "rule": "block-no-empty", + "line": 296, + "col": 20 + }, + { + "rule": "block-no-empty", + "line": 300, + "col": 10 + }, + { + "rule": "block-no-empty", + "line": 304, + "col": 12 + }, + { + "rule": "block-no-empty", + "line": 308, + "col": 29 + }, + { + "rule": "block-no-empty", + "line": 312, + "col": 18 + }, + { + "rule": "block-no-empty", + "line": 316, + "col": 5 + }, + { + "rule": "block-no-empty", + "line": 32, + "col": 15 + }, + { + "rule": "block-no-empty", + "line": 36, + "col": 17 + }, + { + "rule": "block-no-empty", + "line": 40, + "col": 37 + }, + { + "rule": "block-no-empty", + "line": 45, + "col": 26 + }, + { + "rule": "block-no-empty", + "line": 49, + "col": 20 + }, + { + "rule": "block-no-empty", + "line": 53, + "col": 22 + }, + { + "rule": "block-no-empty", + "line": 57, + "col": 11 + }, + { + "rule": "block-no-empty", + "line": 61, + "col": 44 + }, + { + "rule": "block-no-empty", + "line": 65, + "col": 28 + }, + { + "rule": "block-no-redundant-nested-style-rules", + "line": 242, + "col": 3 + }, + { + "rule": "color-function-notation", + "line": 83, + "col": 10 + }, + { + "rule": "color-function-notation", + "line": 86, + "col": 10 + }, + { + "rule": "color-function-notation", + "line": 89, + "col": 10 + }, + { + "rule": "color-hex-length", + "line": 80, + "col": 10 + }, + { + "rule": "color-named", + "line": 9, + "col": 10 + }, + { + "rule": "color-named", + "line": 71, + "col": 10 + }, + { + "rule": "color-named", + "line": 95, + "col": 10 + }, + { + "rule": "color-named", + "line": 109, + "col": 10 + }, + { + "rule": "color-named", + "line": 114, + "col": 15 + }, + { + "rule": "color-named", + "line": 136, + "col": 10 + }, + { + "rule": "color-named", + "line": 139, + "col": 10 + }, + { + "rule": "color-named", + "line": 144, + "col": 10 + }, + { + "rule": "color-named", + "line": 147, + "col": 10 + }, + { + "rule": "color-named", + "line": 152, + "col": 10 + }, + { + "rule": "color-named", + "line": 153, + "col": 10 + }, + { + "rule": "color-named", + "line": 158, + "col": 21 + }, + { + "rule": "color-named", + "line": 159, + "col": 15 + }, + { + "rule": "color-named", + "line": 185, + "col": 36 + }, + { + "rule": "color-named", + "line": 185, + "col": 41 + }, + { + "rule": "color-named", + "line": 240, + "col": 10 + }, + { + "rule": "color-named", + "line": 243, + "col": 12 + }, + { + "rule": "color-named", + "line": 250, + "col": 10 + }, + { + "rule": "color-named", + "line": 255, + "col": 10 + }, + { + "rule": "color-named", + "line": 261, + "col": 11 + }, + { + "rule": "color-named", + "line": 323, + "col": 10 + }, + { + "rule": "color-no-invalid-hex", + "line": 77, + "col": 10 + }, + { + "rule": "comment-no-empty", + "line": 100, + "col": 1 + }, + { + "rule": "comment-whitespace-inside", + "line": 100, + "col": 4 + }, + { + "rule": "comment-whitespace-inside", + "line": 100, + "col": 1 + }, + { + "rule": "comment-whitespace-inside", + "line": 103, + "col": 3 + }, + { + "rule": "comment-whitespace-inside", + "line": 103, + "col": 9 + }, + { + "rule": "custom-property-no-missing-var-function", + "line": 119, + "col": 10 + }, + { + "rule": "declaration-block-no-duplicate-custom-properties", + "line": 123, + "col": 3 + }, + { + "rule": "declaration-block-no-duplicate-properties", + "line": 77, + "col": 3 + }, + { + "rule": "declaration-block-no-duplicate-properties", + "line": 152, + "col": 3 + }, + { + "rule": "declaration-block-no-shorthand-property-overrides", + "line": 159, + "col": 3 + }, + { + "rule": "font-family-name-quotes", + "line": 173, + "col": 16 + }, + { + "rule": "font-family-no-duplicate-names", + "line": 165, + "col": 23 + }, + { + "rule": "font-family-no-missing-generic-family-keyword", + "line": 165, + "col": 23 + }, + { + "rule": "font-family-no-missing-generic-family-keyword", + "line": 168, + "col": 16 + }, + { + "rule": "font-family-no-missing-generic-family-keyword", + "line": 173, + "col": 16 + }, + { + "rule": "function-calc-no-unspaced-operator", + "line": 182, + "col": 18 + }, + { + "rule": "function-calc-no-unspaced-operator", + "line": 182, + "col": 18 + }, + { + "rule": "function-linear-gradient-no-nonstandard-direction", + "line": 185, + "col": 31 + }, + { + "rule": "function-no-unknown", + "line": 179, + "col": 10 + }, + { + "rule": "hue-degree-notation", + "line": 89, + "col": 14 + }, + { + "rule": "import-notation", + "line": 6, + "col": 9 + }, + { + "rule": "import-notation", + "line": 131, + "col": 9 + }, + { + "rule": "import-notation", + "line": 132, + "col": 9 + }, + { + "rule": "keyframe-block-no-duplicate-selectors", + "line": 205, + "col": 3 + }, + { + "rule": "keyframe-declaration-no-important", + "line": 213, + "col": 16 + }, + { + "rule": "keyframe-selector-notation", + "line": 202, + "col": 3 + }, + { + "rule": "keyframe-selector-notation", + "line": 205, + "col": 3 + }, + { + "rule": "keyframe-selector-notation", + "line": 212, + "col": 3 + }, + { + "rule": "keyframe-selector-notation", + "line": 219, + "col": 3 + }, + { + "rule": "length-zero-no-unit", + "line": 227, + "col": 12 + }, + { + "rule": "media-feature-name-no-unknown", + "line": 45, + "col": 9 + }, + { + "rule": "media-feature-name-no-vendor-prefix", + "line": 61, + "col": 9 + }, + { + "rule": "media-query-no-invalid", + "line": 53, + "col": 8 + }, + { + "rule": "named-grid-areas-no-invalid", + "line": 196, + "col": 5 + }, + { + "rule": "no-descending-specificity", + "line": 138, + "col": 1 + }, + { + "rule": "no-duplicate-at-import-rules", + "line": 132, + "col": 1 + }, + { + "rule": "no-duplicate-selectors", + "line": 70, + "col": 1 + }, + { + "rule": "no-duplicate-selectors", + "line": 75, + "col": 1 + }, + { + "rule": "no-duplicate-selectors", + "line": 93, + "col": 1 + }, + { + "rule": "no-duplicate-selectors", + "line": 117, + "col": 1 + }, + { + "rule": "no-duplicate-selectors", + "line": 146, + "col": 1 + }, + { + "rule": "no-duplicate-selectors", + "line": 163, + "col": 1 + }, + { + "rule": "no-duplicate-selectors", + "line": 177, + "col": 1 + }, + { + "rule": "no-duplicate-selectors", + "line": 193, + "col": 1 + }, + { + "rule": "no-duplicate-selectors", + "line": 225, + "col": 1 + }, + { + "rule": "no-duplicate-selectors", + "line": 233, + "col": 1 + }, + { + "rule": "no-duplicate-selectors", + "line": 239, + "col": 1 + }, + { + "rule": "no-duplicate-selectors", + "line": 242, + "col": 3 + }, + { + "rule": "no-duplicate-selectors", + "line": 259, + "col": 1 + }, + { + "rule": "no-duplicate-selectors", + "line": 273, + "col": 1 + }, + { + "rule": "no-duplicate-selectors", + "line": 279, + "col": 1 + }, + { + "rule": "no-duplicate-selectors", + "line": 285, + "col": 1 + }, + { + "rule": "no-duplicate-selectors", + "line": 333, + "col": 1 + }, + { + "rule": "no-duplicate-selectors", + "line": 340, + "col": 1 + }, + { + "rule": "no-invalid-double-slash-comments", + "line": 106, + "col": 1 + }, + { + "rule": "no-invalid-position-at-import-rule", + "line": 13, + "col": 1 + }, + { + "rule": "no-invalid-position-at-import-rule", + "line": 131, + "col": 1 + }, + { + "rule": "no-invalid-position-at-import-rule", + "line": 132, + "col": 1 + }, + { + "rule": "no-irregular-whitespace", + "line": 341, + "col": 12 + }, + { + "rule": "no-unknown-animations", + "line": 188, + "col": 14 + }, + { + "rule": "no-unknown-custom-media", + "line": 65, + "col": 9 + }, + { + "rule": "no-unknown-custom-properties", + "line": 126, + "col": 19 + }, + { + "rule": "number-max-precision", + "line": 230, + "col": 10 + }, + { + "rule": "property-no-unknown", + "line": 261, + "col": 3 + }, + { + "rule": "property-no-vendor-prefix", + "line": 267, + "col": 3 + }, + { + "rule": "selector-anb-no-unmatchable", + "line": 312, + "col": 3 + }, + { + "rule": "selector-no-vendor-prefix", + "line": 308, + "col": 1 + }, + { + "rule": "selector-pseudo-class-no-unknown", + "line": 292, + "col": 2 + }, + { + "rule": "selector-pseudo-element-colon-notation", + "line": 300, + "col": 2 + }, + { + "rule": "selector-pseudo-element-no-unknown", + "line": 296, + "col": 2 + }, + { + "rule": "selector-type-no-unknown", + "line": 304, + "col": 1 + }, + { + "rule": "shorthand-property-no-redundant-values", + "line": 287, + "col": 11 + }, + { + "rule": "string-no-newline", + "line": 321, + "col": 9 + }, + { + "rule": "unit-no-unknown", + "line": 173, + "col": 17 + }, + { + "rule": "unit-no-unknown", + "line": 235, + "col": 12 + }, + { + "rule": "value-no-vendor-prefix", + "line": 335, + "col": 12 + } + ], + "empty-source.css": [ + { + "rule": "no-empty-source", + "line": 1, + "col": 1 + } + ] + }, + "rule_locations": { + "alpha-value-notation": [ + { + "file": "common.css", + "line": 86, + "col": 24 + } + ], + "annotation-no-unknown": [ + { + "file": "common.css", + "line": 71, + "col": 14 + } + ], + "at-rule-no-unknown": [ + { + "file": "common.css", + "line": 32, + "col": 1 + } + ], + "at-rule-no-vendor-prefix": [ + { + "file": "common.css", + "line": 40, + "col": 1 + } + ], + "block-no-empty": [ + { + "file": "common.css", + "line": 292, + "col": 18 + }, + { + "file": "common.css", + "line": 296, + "col": 20 + }, + { + "file": "common.css", + "line": 300, + "col": 10 + }, + { + "file": "common.css", + "line": 304, + "col": 12 + }, + { + "file": "common.css", + "line": 308, + "col": 29 + }, + { + "file": "common.css", + "line": 312, + "col": 18 + }, + { + "file": "common.css", + "line": 316, + "col": 5 + }, + { + "file": "common.css", + "line": 32, + "col": 15 + }, + { + "file": "common.css", + "line": 36, + "col": 17 + }, + { + "file": "common.css", + "line": 40, + "col": 37 + }, + { + "file": "common.css", + "line": 45, + "col": 26 + }, + { + "file": "common.css", + "line": 49, + "col": 20 + }, + { + "file": "common.css", + "line": 53, + "col": 22 + }, + { + "file": "common.css", + "line": 57, + "col": 11 + }, + { + "file": "common.css", + "line": 61, + "col": 44 + }, + { + "file": "common.css", + "line": 65, + "col": 28 + } + ], + "block-no-redundant-nested-style-rules": [ + { + "file": "common.css", + "line": 242, + "col": 3 + } + ], + "color-function-notation": [ + { + "file": "common.css", + "line": 83, + "col": 10 + }, + { + "file": "common.css", + "line": 86, + "col": 10 + }, + { + "file": "common.css", + "line": 89, + "col": 10 + } + ], + "color-hex-length": [ + { + "file": "common.css", + "line": 80, + "col": 10 + } + ], + "color-named": [ + { + "file": "common.css", + "line": 9, + "col": 10 + }, + { + "file": "common.css", + "line": 71, + "col": 10 + }, + { + "file": "common.css", + "line": 95, + "col": 10 + }, + { + "file": "common.css", + "line": 109, + "col": 10 + }, + { + "file": "common.css", + "line": 114, + "col": 15 + }, + { + "file": "common.css", + "line": 136, + "col": 10 + }, + { + "file": "common.css", + "line": 139, + "col": 10 + }, + { + "file": "common.css", + "line": 144, + "col": 10 + }, + { + "file": "common.css", + "line": 147, + "col": 10 + }, + { + "file": "common.css", + "line": 152, + "col": 10 + }, + { + "file": "common.css", + "line": 153, + "col": 10 + }, + { + "file": "common.css", + "line": 158, + "col": 21 + }, + { + "file": "common.css", + "line": 159, + "col": 15 + }, + { + "file": "common.css", + "line": 185, + "col": 36 + }, + { + "file": "common.css", + "line": 185, + "col": 41 + }, + { + "file": "common.css", + "line": 240, + "col": 10 + }, + { + "file": "common.css", + "line": 243, + "col": 12 + }, + { + "file": "common.css", + "line": 250, + "col": 10 + }, + { + "file": "common.css", + "line": 255, + "col": 10 + }, + { + "file": "common.css", + "line": 261, + "col": 11 + }, + { + "file": "common.css", + "line": 323, + "col": 10 + } + ], + "color-no-invalid-hex": [ + { + "file": "common.css", + "line": 77, + "col": 10 + } + ], + "comment-no-empty": [ + { + "file": "common.css", + "line": 100, + "col": 1 + } + ], + "comment-whitespace-inside": [ + { + "file": "common.css", + "line": 100, + "col": 4 + }, + { + "file": "common.css", + "line": 100, + "col": 1 + }, + { + "file": "common.css", + "line": 103, + "col": 3 + }, + { + "file": "common.css", + "line": 103, + "col": 9 + } + ], + "custom-property-no-missing-var-function": [ + { + "file": "common.css", + "line": 119, + "col": 10 + } + ], + "declaration-block-no-duplicate-custom-properties": [ + { + "file": "common.css", + "line": 123, + "col": 3 + } + ], + "declaration-block-no-duplicate-properties": [ + { + "file": "common.css", + "line": 77, + "col": 3 + }, + { + "file": "common.css", + "line": 152, + "col": 3 + } + ], + "declaration-block-no-shorthand-property-overrides": [ + { + "file": "common.css", + "line": 159, + "col": 3 + } + ], + "font-family-name-quotes": [ + { + "file": "common.css", + "line": 173, + "col": 16 + } + ], + "font-family-no-duplicate-names": [ + { + "file": "common.css", + "line": 165, + "col": 23 + } + ], + "font-family-no-missing-generic-family-keyword": [ + { + "file": "common.css", + "line": 165, + "col": 23 + }, + { + "file": "common.css", + "line": 168, + "col": 16 + }, + { + "file": "common.css", + "line": 173, + "col": 16 + } + ], + "function-calc-no-unspaced-operator": [ + { + "file": "common.css", + "line": 182, + "col": 18 + }, + { + "file": "common.css", + "line": 182, + "col": 18 + } + ], + "function-linear-gradient-no-nonstandard-direction": [ + { + "file": "common.css", + "line": 185, + "col": 31 + } + ], + "function-no-unknown": [ + { + "file": "common.css", + "line": 179, + "col": 10 + } + ], + "hue-degree-notation": [ + { + "file": "common.css", + "line": 89, + "col": 14 + } + ], + "import-notation": [ + { + "file": "common.css", + "line": 6, + "col": 9 + }, + { + "file": "common.css", + "line": 131, + "col": 9 + }, + { + "file": "common.css", + "line": 132, + "col": 9 + } + ], + "keyframe-block-no-duplicate-selectors": [ + { + "file": "common.css", + "line": 205, + "col": 3 + } + ], + "keyframe-declaration-no-important": [ + { + "file": "common.css", + "line": 213, + "col": 16 + } + ], + "keyframe-selector-notation": [ + { + "file": "common.css", + "line": 202, + "col": 3 + }, + { + "file": "common.css", + "line": 205, + "col": 3 + }, + { + "file": "common.css", + "line": 212, + "col": 3 + }, + { + "file": "common.css", + "line": 219, + "col": 3 + } + ], + "length-zero-no-unit": [ + { + "file": "common.css", + "line": 227, + "col": 12 + } + ], + "media-feature-name-no-unknown": [ + { + "file": "common.css", + "line": 45, + "col": 9 + } + ], + "media-feature-name-no-vendor-prefix": [ + { + "file": "common.css", + "line": 61, + "col": 9 + } + ], + "media-query-no-invalid": [ + { + "file": "common.css", + "line": 53, + "col": 8 + } + ], + "named-grid-areas-no-invalid": [ + { + "file": "common.css", + "line": 196, + "col": 5 + } + ], + "no-descending-specificity": [ + { + "file": "common.css", + "line": 138, + "col": 1 + } + ], + "no-duplicate-at-import-rules": [ + { + "file": "common.css", + "line": 132, + "col": 1 + } + ], + "no-duplicate-selectors": [ + { + "file": "common.css", + "line": 70, + "col": 1 + }, + { + "file": "common.css", + "line": 75, + "col": 1 + }, + { + "file": "common.css", + "line": 93, + "col": 1 + }, + { + "file": "common.css", + "line": 117, + "col": 1 + }, + { + "file": "common.css", + "line": 146, + "col": 1 + }, + { + "file": "common.css", + "line": 163, + "col": 1 + }, + { + "file": "common.css", + "line": 177, + "col": 1 + }, + { + "file": "common.css", + "line": 193, + "col": 1 + }, + { + "file": "common.css", + "line": 225, + "col": 1 + }, + { + "file": "common.css", + "line": 233, + "col": 1 + }, + { + "file": "common.css", + "line": 239, + "col": 1 + }, + { + "file": "common.css", + "line": 242, + "col": 3 + }, + { + "file": "common.css", + "line": 259, + "col": 1 + }, + { + "file": "common.css", + "line": 273, + "col": 1 + }, + { + "file": "common.css", + "line": 279, + "col": 1 + }, + { + "file": "common.css", + "line": 285, + "col": 1 + }, + { + "file": "common.css", + "line": 333, + "col": 1 + }, + { + "file": "common.css", + "line": 340, + "col": 1 + } + ], + "no-invalid-double-slash-comments": [ + { + "file": "common.css", + "line": 106, + "col": 1 + } + ], + "no-invalid-position-at-import-rule": [ + { + "file": "common.css", + "line": 13, + "col": 1 + }, + { + "file": "common.css", + "line": 131, + "col": 1 + }, + { + "file": "common.css", + "line": 132, + "col": 1 + } + ], + "no-irregular-whitespace": [ + { + "file": "common.css", + "line": 341, + "col": 12 + } + ], + "no-unknown-animations": [ + { + "file": "common.css", + "line": 188, + "col": 14 + } + ], + "no-unknown-custom-media": [ + { + "file": "common.css", + "line": 65, + "col": 9 + } + ], + "no-unknown-custom-properties": [ + { + "file": "common.css", + "line": 126, + "col": 19 + } + ], + "number-max-precision": [ + { + "file": "common.css", + "line": 230, + "col": 10 + } + ], + "property-no-unknown": [ + { + "file": "common.css", + "line": 261, + "col": 3 + } + ], + "property-no-vendor-prefix": [ + { + "file": "common.css", + "line": 267, + "col": 3 + } + ], + "selector-anb-no-unmatchable": [ + { + "file": "common.css", + "line": 312, + "col": 3 + } + ], + "selector-no-vendor-prefix": [ + { + "file": "common.css", + "line": 308, + "col": 1 + } + ], + "selector-pseudo-class-no-unknown": [ + { + "file": "common.css", + "line": 292, + "col": 2 + } + ], + "selector-pseudo-element-colon-notation": [ + { + "file": "common.css", + "line": 300, + "col": 2 + } + ], + "selector-pseudo-element-no-unknown": [ + { + "file": "common.css", + "line": 296, + "col": 2 + } + ], + "selector-type-no-unknown": [ + { + "file": "common.css", + "line": 304, + "col": 1 + } + ], + "shorthand-property-no-redundant-values": [ + { + "file": "common.css", + "line": 287, + "col": 11 + } + ], + "string-no-newline": [ + { + "file": "common.css", + "line": 321, + "col": 9 + } + ], + "unit-no-unknown": [ + { + "file": "common.css", + "line": 173, + "col": 17 + }, + { + "file": "common.css", + "line": 235, + "col": 12 + } + ], + "value-no-vendor-prefix": [ + { + "file": "common.css", + "line": 335, + "col": 12 + } + ], + "no-empty-source": [ + { + "file": "empty-source.css", + "line": 1, + "col": 1 + } + ] + } +} \ No newline at end of file