Closed6
Erg用のLinterの関数を実装する
ref to https://github.com/erg-lang/erg/issues/501
とりあえずはこれを作る
- erg/crates/erg_linter/lint.rs
pub fn lint(&mut self, hir: &HIR) -> CompileWarnings {
log!(info "Start linting");
for chunk in hir.module.iter() {
self.lint_too_many_params(chunk);
self.lint_bool_comparison(chunk)
self.lint_too_many_instance_attributes(chunk); // <- here it is
}
log!(info "Finished linting");
self.warns.take()
}
fn lint_too_many_instance_attributes(&mut self, expr: &Expr) {
// errなのは意図的に赤くするため、見にくい人はinfo(緑色)
log!(err {:#?}, expr)
}
erg_linterを実行するコマンドがなかったため、lint
コマンドを追加する
- erg/crates/erg_common/config.rs
pub fn parse() -> Self {
let mut args = env::args();
// --- 省略 ---
"--check" => {
cfg.mode = ErgMode::FullCheck;
}
"--lint" => {
cfg.mode = ErgMode::Lint;
}
"--compile" | "--dump-as-pyc" => {
cfg.mode = ErgMode::Compile;
}
// --- 省略 ---
}
ついでに今回のインスタンス属性ありすぎ問題用のスクリプトを用意する
C = Class {
foo_01 = Int;
foo_02 = Int;
foo_03 = Int;
foo_04 = Int;
foo_05 = Int;
foo_06 = Int;
foo_07 = Int;
foo_08 = Int;
foo_09 = Int;
foo_10 = Int;
foo_11 = Int;
foo_12 = Int;
foo_13 = Int;
foo_14 = Int;
foo_15 = Int;
foo_16 = Int;
foo_17 = Int;
foo_18 = Int;
foo_19 = Int;
foo_20 = Int;
foo_21 = Int;
foo_22 = Int;
foo_23 = Int;
foo_24 = Int;
foo_25 = Int;
foo_26 = Int;
foo_27 = Int;
foo_28 = Int;
foo_29 = Int;
foo_30 = Int;
foo_31 = Int;
foo_32 = Int;
foo_33 = Int;
foo_34 = Int;
foo_35 = Int;
foo_36 = Int;
}
動作確認をする
cargo rd -- --lint too_many.er
行が多いので抜き出した抽象構文木(AST)が以下の通り
ClassDef {
obj: Class(
ClassTypeObj {
t: Mono(
Rc(
"<module>::C",
),
),
base: Some(
Builtin {
t: Record(
Dict {
dict: {
Field {
vis: Private,
symbol: Rc(
"foo_15",
...
],
var_params: None,
default_params: [],
kw_var_params: None,
return_t: Mono(
Rc(
"<module>::C",
),
),
},
),
methods_list: [],
},
ClassDef
のobj: Class
ので進んでいき、t: Record
までくれば良い
ergにおいてClassのインスタンス属性はレコードとして定義されている(参考)ため
これをパターンマッチで取り出していく
fn lint_too_many_instance_attributes(&mut self, expr: &Expr) {
const MAX_INSTANCE_ATTRIBUTES: usize = 36;
if let Expr::ClassDef(ClassDef { obj, .. }) = expr {
if let Some(TypeObj::Builtin {
t: Type::Record(record),
..
}) = obj.base_or_sup()
{
if record.len() >= MAX_INSTANCE_ATTRIBUTES {
// ワーニングの処理
}
}
}
}
warningを定義する
- crates/erg_linter/warn.rs
同じモジュール内の関数をコピーして名前とメッセージを変えてみる
pub(crate) fn too_many_instance_attributes(
input: Input,
caused_by: String,
loc: Location,
) -> CompileWarning {
CompileWarning::new(
ErrorCore::new(
vec![SubMessage::ambiguous_new(loc, vec![], Some("hint".to_string()))],
"msg here".to_string(),
0,
ErrorKind::AttributeWarning,
loc,
),
input,
caused_by,
)
}
- crates/erg_linter/lint.rs
先程のlinterメソッドの中で呼び出す
fn lint_too_many_instance_attributes(&mut self, expr: &Expr) {
const MAX_INSTANCE_ATTRIBUTES: usize = 36;
if let Expr::ClassDef(ClassDef { obj, .. }) = expr {
if let Some(TypeObj::Builtin {
t: Type::Record(record),
..
}) = obj.base_or_sup()
{
if record.len() >= MAX_INSTANCE_ATTRIBUTES {
// --- from ---
self.warns.push(too_many_instance_attributes(
self.input(),
self.caused_by(),
expr.loc(),
));
// --- here ---
}
}
}
}
再度動作チェックをしてみる
cargo rd -- --lint too_many.er
Warning[#0000]: File .\tests\linter\too_many.er, line 1, <module>
1 | C = Class {
: -
: `- hint
AttributeWarning: msg here
こういったときにTDD開発みたいなやり方ができると良いなあと思う
TODOとして以下のようなのを考える(注意: TDDのやり方ではない)
- hintメッセージを書く
- warningメッセージを書く
-
メッセージを多言語対応する(switch_lang)
- english
- japanese
- simplified_chinese
- traditional_chinese
- crates/erg_linter/lint.rs
メインのメッセージは今回の実装目的の内容をかく
ヒントはちょっとめんどくさく、現状当たり障りのないことしか書けない
pub(crate) fn too_many_instance_attributes(
input: Input,
caused_by: String,
loc: Location,
) -> CompileWarning {
let msg = switch_lang!(
"japanese" => "インスタンス属性が多すぎます",
"simplified_chinese" => "实例属性过多",
"traditional_chinese" => "實例屬性過多",
"english" => "too many instance attributes",
)
.to_string();
let hint = switch_lang!(
"japanese" => "サブクラスやデータクラスを活用してください",
"simplified_chinese" => "利用子类和数据类",
"traditional_chinese" => "利用子類和資料類",
"english" => "take advantage of subclasses or data classes",
)
.to_string();
CompileWarning::new(
ErrorCore::new(
vec![SubMessage::ambiguous_new(loc, vec![], Some(hint))],
msg,
0,
ErrorKind::AttributeWarning,
loc,
),
input,
caused_by,
)
実行して試してみる
# [alias]
# r = "run"
# r_ja = "run --features japanese"
# r_zh_cn = "run --features simplified_chinese"
# r_zh_tw = "run --features traditional_chinese"
cargo r_ja -- --lint .\tests\linter\too_many.er
Warning[#000]: File .\tests\linter\too_many.er, line 1, <module>
1 | C = Class {
: -
: `- サブクラスやデータクラスを活用してください
AttributeWarning: インスタンス属性が多すぎます
cargo r_zh_cn -- --lint .\tests\linter\too_many.er
Warning[#0000]: File .\tests\linter\too_many.er, line 1, <module>
1 | C = Class {
: -
: `- 利用子类和数据类
AttributeWarning: 实例属性过多
- line!()
line!()を使い実行時のファイルの行を入れている
- crates/erg_linter/lint.rs
self.warns.push(too_many_instance_attributes(
self.input(),
line!() as usize, <- here
self.caused_by(),
expr.loc(),
));
- crates/erg_linter/warn.rs
pub(crate) fn too_many_instance_attributes(
input: Input,
errno: usize,
caused_by: String,
loc: Location,
) -> CompileWarning {
// 略
ErrorCore::new(
vec![SubMessage::ambiguous_new(loc, vec![], Some(hint))],
msg,
errno, <- here
ErrorKind::AttributeWarning,
loc,
),
最終出力結果が以下のようになる
Warning[#0233]: File .\tests\linter\too_many.er, line 1, <module>
1 | C = Class {
: -
: `- サブクラスやデータクラスを活用してください
AttributeWarning: インスタンス属性が多すぎます
このスクラップは6ヶ月前にクローズされました