docs: 归档 demo 工程样例与插件实测报告

- 新增三个 demo 工程(demo-eslint / demo-sqlfluff / demo-stylelint):各含 src/ 样例代码与覆盖率映射(run-coverage.mjs、coverage_map.json、stylelint_line_map.json)

- 归档各 demo 的插件实测导出报告(reports/):

  - demo-eslint:common.js / esm-demo.js / legacysyntax.cjs / typescript.ts 审查报告 + 覆盖率报告(292/292 条零偏差,126 种规则全覆盖)

  - demo-sqlfluff:11 个 SQL 样例审查报告 + 覆盖率报告(113/113 条零偏差,57 种规则全覆盖,含 JJ01 行号缺陷修复后的复测验证)

  - demo-stylelint:common.css / empty-source.css 审查报告 + 覆盖率报告(145/145 条零偏差,68 种规则全覆盖)
This commit is contained in:
范智鹏
2026-08-25 22:00:19 +08:00
parent 9c676e46e6
commit c92249a044
43 changed files with 6103 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
{
"dependencies": {
"@eslint/js": "^10.0.1",
"typescript-eslint": "^8.66.0"
}
}
+662
View File
@@ -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;`
@@ -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`*
@@ -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 移到函数内部或通过导出接口提供;若仅是调试用途,在提交前移除。
@@ -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 */`
@@ -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<string, unknown>` 并通过 `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` 约束,直接写成 `<T>`
- 🔴 `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。
+166
View File
@@ -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];
// 候选 tokenkebab-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');
+608
View File
@@ -0,0 +1,608 @@
// ============ common.js(合并文件) ============
// 由 ESLint 演示工程原 10 个 JS 文件合并压缩而来,用于触发各条 ESLint 规则。
// 保留独立文件:legacysyntax.jsscript 模式解析)、typescript.ts / typesmodule.tsTS 规则)。
// 注:本文件为普通 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 模块文件)演示,此处移除占位。
+11
View File
@@ -0,0 +1,11 @@
// ============ esm-demo.js ============
// ESM 模块文件:演示 no-import-assignmodule 模式下对 import 绑定赋值)
// no-import-assign
import fs from 'fs';
function reassignImport() {
fs = 1;
}
console.log(fs);
+36
View File
@@ -0,0 +1,36 @@
/* eslint preserve-caught-error: "error" */
// ============ legacysyntax.cjs ============
// .cjs 扩展名使 ESLint flat config 默认按 commonjs(宽松)模式解析本文件,
// 无需任何工程配置(eslint.config.mjs)即可触发仅 sloppy 模式合法的旧式语法规则。
// 首行行内注释单独启用 preserve-caught-erroreslint 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-errorcatch 中抛新错误应保留原始错误)
try {
JSON.parse('not json');
} catch (err) {
throw new Error('parse failed');
}
console.log(nonOctalEscape, legacyOctal, duplicateArgs(1, 2));
+34
View File
@@ -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-errorESLint 10 新增,catch 中抛新错误应保留原始错误)
try {
JSON.parse('not json');
} catch (err) {
throw new Error('parse failed');
}
console.log(nonOctalEscape, legacyOctal, duplicateArgs(1, 2));
+213
View File
@@ -0,0 +1,213 @@
// ============ typescript.ts ============
// TypeScript 规则演示(覆盖 ts.configs.recommended + 插件精选规则)
// triple-slash-reference: 禁止非类型导入的三斜线指令
/// <reference path="./typesmodule.ts" />
// 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<string, unknown>, 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 extends unknown>(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-errorESLint 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,
};
+8
View File
@@ -0,0 +1,8 @@
// ============ typesmodule.ts ============
// 供 typescript.ts 使用的类型模块
export interface stringifyType {
(input: string): string;
}
export type DemoType = string | number;
+290
View File
@@ -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"
]
}
}
@@ -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`
@@ -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),这些注释就不再准确,会误导后续阅读者。
建议: 在应用修复后同步更新或删除对应注释;或将原始违规样例与修复后样例分开存放,并注明预期触发的规则。
@@ -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 默认 30Oracle 要求必须指定),若 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 也可接受。
@@ -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 的列。
@@ -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`
@@ -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;,以更高效、清晰地表达去重语义。
@@ -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,以明确表归属。
@@ -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_ColSELECT "SIMPLE_COL" 会失败;RF06 只提示引号多余,未覆盖这一大小写风险。
建议: 先确认数据字典中的真实列名。若列名确为 SIMPLE_COL,直接去掉引号;否则改成与实际列名一致的大小写并去掉引号。
- 🔵 [AI] [design] `demo-comment-incomplete` L1
**演示注释未列出实际触发的全部规则**
第 1 行注释声明仅演示 RF01、RF02、RF04、RF05、RF06,但静态分析还发现 AM05、LT02、LT12;如果这是规则演示文件,注释不完整且会误导维护者。
建议: 更新注释以列出所有实际触发的规则,或修复 AM05/LT02/LT12 等非预期问题,使文件只演示声明的规则。
@@ -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)`
@@ -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 名)。
@@ -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(或根据业务选择排序列)。
@@ -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%
| 分组 | 规则数 | 触发规则明细 |
|---|---:|---|
| ALaliasing 别名) | 8 | AL02、AL03、AL04、AL05、AL06、AL08、AL09、AL10 |
| AMambiguous 歧义) | 6 | AM01、AM02、AM03、AM05、AM06、AM08 |
| CPcapitalisation 大小写) | 5 | CP01、CP02、CP03、CP04、CP05 |
| CVconvention 约定) | 8 | CV01、CV02、CV03、CV04、CV05、CV06、CV08、CV12 |
| JJjinja 模板) | 1 | JJ01 |
| LTlayout 布局) | 12 | LT01、LT02、LT05、LT06、LT07、LT08、LT10、LT11、LT12、LT13、LT14、LT15 |
| RFreferences 引用) | 5 | RF01、RF02、RF04、RF05、RF06 |
| STstructure 结构) | 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. **AM02ambiguous.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. **AL06aliasing.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 L6sqlfluff 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.04.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`*
+47
View File
@@ -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
+65
View File
@@ -0,0 +1,65 @@
-- Demo: ambiguity rules (AM01, AM02, AM03, AM05, AM06, AM08)
-- 注:AM02ambiguous.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)
+40
View File
@@ -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
+63
View File
@@ -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)
+42
View File
@@ -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
@@ -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
@@ -0,0 +1,4 @@
SELECT id FROM my_table;
+32
View File
@@ -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
+101
View File
@@ -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
+6
View File
@@ -0,0 +1,6 @@
-- Demo: jinja padding rule (JJ01)
-- {{1}}
SELECT
id
FROM
my_table;
@@ -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)
@@ -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
非法的语法字符串“<invalid>”。
建议: @property 的 syntax 必须使用合法的 CSS 语法关键字,如 <length>、<color>、<number>。
- 🔴 `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 位于非法位置,会被浏览器忽略,导致依赖样式缺失。
建议: 改用 <link rel="stylesheet"> 在 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 编码。
@@ -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** | **68100%** | — |
**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.cssbug 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 条全部 errorrecommended 与 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`*
@@ -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(或所用预处理器)样式规则,使其不再为空。
+342
View File
@@ -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: "<invalid>";
inherits: false;
}
/* ===== 原 vendor-value.css ===== */
a {
/* value-no-vendor-prefix: 带厂商前缀的值 */
display: -webkit-flex;
}
/* ===== 原 whitespace.css ===== */
/* no-irregular-whitespace: value contains NBSP */
a {
margin: 0 ;
}
+1448
View File
@@ -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
}
]
}
}