392 lines
13 KiB
Rust
392 lines
13 KiB
Rust
//! NAC 去以太坊化 Lint 检查器
|
||
//!
|
||
//! 在 CNNL 编译器中检测并自动纠正以太坊化术语,
|
||
//! 防止开发者误用 RPC、EVM、Solidity 等以太坊概念。
|
||
//!
|
||
//! # 设计原则
|
||
//! - 输入 RPC → 错误,建议使用 NAC Lens
|
||
//! - 输入 EVM → 错误,建议使用 NVM
|
||
//! - 输入 Solidity → 错误,建议使用 Charter
|
||
//! - 输入 JSON-RPC → 错误,建议使用 NAC Lens 协议
|
||
//! - 输入 eth_ / net_ / web3_ 方法前缀 → 错误,建议使用 nac_ 方法
|
||
|
||
use std::collections::HashMap;
|
||
|
||
/// 以太坊化术语检测规则
|
||
#[derive(Debug, Clone)]
|
||
pub struct EthTermRule {
|
||
/// 错误的以太坊术语
|
||
pub eth_term: &'static str,
|
||
/// 正确的 NAC 术语
|
||
pub nac_term: &'static str,
|
||
/// 错误说明
|
||
pub message: &'static str,
|
||
/// 是否大小写不敏感匹配
|
||
pub case_insensitive: bool,
|
||
}
|
||
|
||
/// NAC 去以太坊化 Lint 诊断
|
||
#[derive(Debug, Clone)]
|
||
pub struct EthLintDiagnostic {
|
||
/// 发现位置(字节偏移)
|
||
pub span: std::ops::Range<usize>,
|
||
/// 发现的以太坊术语
|
||
pub found: String,
|
||
/// 建议的 NAC 术语
|
||
pub suggestion: String,
|
||
/// 错误消息
|
||
pub message: String,
|
||
/// 是否可自动修复
|
||
pub auto_fixable: bool,
|
||
}
|
||
|
||
impl std::fmt::Display for EthLintDiagnostic {
|
||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||
write!(
|
||
f,
|
||
"[NAC-LINT-E001] 去以太坊化错误:在 {}..{} 发现 '{}'\n\
|
||
→ 错误:{}\n\
|
||
→ 建议:将 '{}' 替换为 '{}'",
|
||
self.span.start,
|
||
self.span.end,
|
||
self.found,
|
||
self.message,
|
||
self.found,
|
||
self.suggestion
|
||
)
|
||
}
|
||
}
|
||
|
||
/// NAC 去以太坊化 Lint 检查器
|
||
pub struct NacDeEthLint {
|
||
rules: Vec<EthTermRule>,
|
||
}
|
||
|
||
impl NacDeEthLint {
|
||
/// 创建标准 NAC 去以太坊化检查器
|
||
pub fn new() -> Self {
|
||
let rules = vec![
|
||
// RPC 相关
|
||
EthTermRule {
|
||
eth_term: "RPC",
|
||
nac_term: "NAC Lens",
|
||
message: "NAC 公链不使用 RPC 协议。NAC 原生网络协议为 NAC Lens(元协议文明网络栈)",
|
||
case_insensitive: false,
|
||
},
|
||
EthTermRule {
|
||
eth_term: "rpc",
|
||
nac_term: "nac_lens",
|
||
message: "NAC 公链不使用 rpc。请使用 NAC Lens 协议(nac_lens)",
|
||
case_insensitive: false,
|
||
},
|
||
EthTermRule {
|
||
eth_term: "NRPC",
|
||
nac_term: "NAC Lens",
|
||
message: "NRPC 已更名为 NAC Lens。请使用 NAC Lens 协议",
|
||
case_insensitive: false,
|
||
},
|
||
EthTermRule {
|
||
eth_term: "JSON-RPC",
|
||
nac_term: "NAC Lens",
|
||
message: "NAC 公链不使用 JSON-RPC(以太坊协议)。请使用 NAC Lens 原生协议",
|
||
case_insensitive: true,
|
||
},
|
||
EthTermRule {
|
||
eth_term: "jsonrpc",
|
||
nac_term: "nac_lens",
|
||
message: "NAC 公链不使用 jsonrpc 字段(以太坊 JSON-RPC 格式)。请使用 NAC Lens 请求格式",
|
||
case_insensitive: false,
|
||
},
|
||
// 虚拟机相关
|
||
EthTermRule {
|
||
eth_term: "EVM",
|
||
nac_term: "NVM",
|
||
message: "NAC 公链不使用 EVM(以太坊虚拟机)。NAC 原生虚拟机为 NVM(NAC Virtual Machine)",
|
||
case_insensitive: false,
|
||
},
|
||
EthTermRule {
|
||
eth_term: "evm",
|
||
nac_term: "nvm",
|
||
message: "NAC 公链不使用 evm。请使用 NVM(nvm)",
|
||
case_insensitive: false,
|
||
},
|
||
// 智能合约语言
|
||
EthTermRule {
|
||
eth_term: "Solidity",
|
||
nac_term: "Charter",
|
||
message: "NAC 公链不使用 Solidity(以太坊合约语言)。NAC 原生智能合约语言为 Charter",
|
||
case_insensitive: false,
|
||
},
|
||
EthTermRule {
|
||
eth_term: "solidity",
|
||
nac_term: "charter",
|
||
message: "NAC 公链不使用 solidity。请使用 Charter 合约语言",
|
||
case_insensitive: false,
|
||
},
|
||
// 共识机制
|
||
EthTermRule {
|
||
eth_term: "PoS",
|
||
nac_term: "CBPP",
|
||
message: "NAC 公链不使用 PoS(权益证明)。NAC 原生共识为 CBPP(宪政区块生产协议)",
|
||
case_insensitive: false,
|
||
},
|
||
EthTermRule {
|
||
eth_term: "PoW",
|
||
nac_term: "CBPP",
|
||
message: "NAC 公链不使用 PoW(工作量证明)。NAC 原生共识为 CBPP",
|
||
case_insensitive: false,
|
||
},
|
||
// 网络协议
|
||
EthTermRule {
|
||
eth_term: "P2P",
|
||
nac_term: "CSNP",
|
||
message: "NAC 公链不使用传统 P2P 网络。NAC 原生网络协议为 CSNP(宪政感知神经网络协议)",
|
||
case_insensitive: false,
|
||
},
|
||
// 以太坊方法前缀
|
||
EthTermRule {
|
||
eth_term: "eth_",
|
||
nac_term: "nac_",
|
||
message: "NAC 公链不使用 eth_ 方法前缀(以太坊 JSON-RPC)。请使用 nac_ 方法前缀",
|
||
case_insensitive: false,
|
||
},
|
||
EthTermRule {
|
||
eth_term: "net_version",
|
||
nac_term: "nac_chainId",
|
||
message: "NAC 公链不使用 net_version(以太坊方法)。请使用 nac_chainId",
|
||
case_insensitive: false,
|
||
},
|
||
EthTermRule {
|
||
eth_term: "web3_",
|
||
nac_term: "nac_",
|
||
message: "NAC 公链不使用 web3_ 方法前缀。请使用 nac_ 方法前缀",
|
||
case_insensitive: false,
|
||
},
|
||
// 代币标准
|
||
EthTermRule {
|
||
eth_term: "ERC20",
|
||
nac_term: "ACC-20",
|
||
message: "NAC 公链不使用 ERC20(以太坊代币标准)。NAC 原生代币标准为 ACC-20",
|
||
case_insensitive: false,
|
||
},
|
||
EthTermRule {
|
||
eth_term: "ERC-20",
|
||
nac_term: "ACC-20",
|
||
message: "NAC 公链不使用 ERC-20。NAC 原生代币标准为 ACC-20",
|
||
case_insensitive: false,
|
||
},
|
||
EthTermRule {
|
||
eth_term: "ERC721",
|
||
nac_term: "ACC-721",
|
||
message: "NAC 公链不使用 ERC721(以太坊 NFT 标准)。NAC 原生 NFT 标准为 ACC-721",
|
||
case_insensitive: false,
|
||
},
|
||
// 地址类型
|
||
EthTermRule {
|
||
eth_term: "address(20)",
|
||
nac_term: "Address(32)",
|
||
message: "NAC 地址为 32 字节,不是以太坊的 20 字节地址",
|
||
case_insensitive: false,
|
||
},
|
||
];
|
||
|
||
Self { rules }
|
||
}
|
||
|
||
/// 对源代码进行去以太坊化检查
|
||
pub fn check(&self, source: &str) -> Vec<EthLintDiagnostic> {
|
||
let mut diagnostics = Vec::new();
|
||
|
||
for rule in &self.rules {
|
||
let term = rule.eth_term;
|
||
let search = if rule.case_insensitive {
|
||
source.to_lowercase()
|
||
} else {
|
||
source.to_string()
|
||
};
|
||
let search_term = if rule.case_insensitive {
|
||
term.to_lowercase()
|
||
} else {
|
||
term.to_string()
|
||
};
|
||
|
||
let mut start = 0;
|
||
while let Some(pos) = search[start..].find(&search_term) {
|
||
let abs_pos = start + pos;
|
||
let end_pos = abs_pos + term.len();
|
||
|
||
// 检查是否是完整词(避免误匹配子字符串)
|
||
let before_ok = abs_pos == 0
|
||
|| !source[..abs_pos]
|
||
.chars()
|
||
.last()
|
||
.map(|c| c.is_alphanumeric() || c == '_')
|
||
.unwrap_or(false);
|
||
|
||
let after_ok = end_pos >= source.len()
|
||
|| !source[end_pos..]
|
||
.chars()
|
||
.next()
|
||
.map(|c| c.is_alphanumeric() || c == '_')
|
||
.unwrap_or(false)
|
||
|| term.ends_with('_'); // 前缀匹配(如 eth_)
|
||
|
||
if before_ok && after_ok {
|
||
diagnostics.push(EthLintDiagnostic {
|
||
span: abs_pos..end_pos,
|
||
found: source[abs_pos..end_pos].to_string(),
|
||
suggestion: rule.nac_term.to_string(),
|
||
message: rule.message.to_string(),
|
||
auto_fixable: true,
|
||
});
|
||
}
|
||
|
||
start = abs_pos + 1;
|
||
}
|
||
}
|
||
|
||
// 按位置排序
|
||
diagnostics.sort_by_key(|d| d.span.start);
|
||
diagnostics
|
||
}
|
||
|
||
/// 自动修复:将所有以太坊化术语替换为 NAC 术语
|
||
pub fn auto_fix(&self, source: &str) -> (String, Vec<EthLintDiagnostic>) {
|
||
let diagnostics = self.check(source);
|
||
if diagnostics.is_empty() {
|
||
return (source.to_string(), diagnostics);
|
||
}
|
||
|
||
// 构建替换映射(按长度降序,避免短词替换长词的子串)
|
||
let mut replacement_map: HashMap<&str, &str> = HashMap::new();
|
||
for rule in &self.rules {
|
||
replacement_map.insert(rule.eth_term, rule.nac_term);
|
||
}
|
||
|
||
let mut result = source.to_string();
|
||
// 从后往前替换,避免位置偏移
|
||
let mut sorted_diags = diagnostics.clone();
|
||
sorted_diags.sort_by_key(|d| std::cmp::Reverse(d.span.start));
|
||
|
||
for diag in &sorted_diags {
|
||
if diag.auto_fixable {
|
||
result.replace_range(diag.span.clone(), &diag.suggestion);
|
||
}
|
||
}
|
||
|
||
(result, diagnostics)
|
||
}
|
||
|
||
/// 生成人类可读的错误报告
|
||
pub fn report(&self, source: &str, filename: &str) -> String {
|
||
let diagnostics = self.check(source);
|
||
if diagnostics.is_empty() {
|
||
return format!("✅ [NAC-LINT] {} 通过去以太坊化检查,无以太坊化术语", filename);
|
||
}
|
||
|
||
let mut report = format!(
|
||
"❌ [NAC-LINT] {} 发现 {} 处以太坊化术语,必须修正:\n\n",
|
||
filename,
|
||
diagnostics.len()
|
||
);
|
||
|
||
for (i, diag) in diagnostics.iter().enumerate() {
|
||
// 计算行号
|
||
let line_num = source[..diag.span.start].chars().filter(|&c| c == '\n').count() + 1;
|
||
report.push_str(&format!(
|
||
" [{}/{}] 第{}行:{}\n",
|
||
i + 1,
|
||
diagnostics.len(),
|
||
line_num,
|
||
diag
|
||
));
|
||
report.push('\n');
|
||
}
|
||
|
||
report.push_str("\n💡 运行 `cnnl-compiler --fix` 可自动修正所有可修复的问题");
|
||
report
|
||
}
|
||
}
|
||
|
||
impl Default for NacDeEthLint {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_rpc_detection() {
|
||
let lint = NacDeEthLint::new();
|
||
let source = "connect to RPC endpoint";
|
||
let diags = lint.check(source);
|
||
assert!(!diags.is_empty(), "应检测到 RPC");
|
||
assert_eq!(diags[0].suggestion, "NAC Lens");
|
||
}
|
||
|
||
#[test]
|
||
fn test_evm_detection() {
|
||
let lint = NacDeEthLint::new();
|
||
let source = "deploy to EVM";
|
||
let diags = lint.check(source);
|
||
assert!(!diags.is_empty(), "应检测到 EVM");
|
||
assert_eq!(diags[0].suggestion, "NVM");
|
||
}
|
||
|
||
#[test]
|
||
fn test_solidity_detection() {
|
||
let lint = NacDeEthLint::new();
|
||
let source = "write Solidity contract";
|
||
let diags = lint.check(source);
|
||
assert!(!diags.is_empty(), "应检测到 Solidity");
|
||
assert_eq!(diags[0].suggestion, "Charter");
|
||
}
|
||
|
||
#[test]
|
||
fn test_eth_method_prefix() {
|
||
let lint = NacDeEthLint::new();
|
||
let source = r#"call "eth_blockNumber""#;
|
||
let diags = lint.check(source);
|
||
assert!(!diags.is_empty(), "应检测到 eth_ 前缀");
|
||
assert_eq!(diags[0].suggestion, "nac_");
|
||
}
|
||
|
||
#[test]
|
||
fn test_auto_fix() {
|
||
let lint = NacDeEthLint::new();
|
||
let source = "use RPC to connect to EVM";
|
||
let (fixed, diags) = lint.auto_fix(source);
|
||
assert!(!diags.is_empty());
|
||
assert!(!fixed.contains("RPC") || fixed.contains("NAC Lens"));
|
||
}
|
||
|
||
#[test]
|
||
fn test_clean_source() {
|
||
let lint = NacDeEthLint::new();
|
||
let source = "use NAC Lens to connect to NVM via Charter";
|
||
let diags = lint.check(source);
|
||
assert!(diags.is_empty(), "干净的 NAC 代码不应有警告");
|
||
}
|
||
|
||
#[test]
|
||
fn test_nrpc_detection() {
|
||
let lint = NacDeEthLint::new();
|
||
let source = "NRPC protocol version";
|
||
let diags = lint.check(source);
|
||
assert!(!diags.is_empty(), "应检测到 NRPC");
|
||
assert_eq!(diags[0].suggestion, "NAC Lens");
|
||
}
|
||
|
||
#[test]
|
||
fn test_erc20_detection() {
|
||
let lint = NacDeEthLint::new();
|
||
let source = "implement ERC20 token";
|
||
let diags = lint.check(source);
|
||
assert!(!diags.is_empty(), "应检测到 ERC20");
|
||
assert_eq!(diags[0].suggestion, "ACC-20");
|
||
}
|
||
}
|