5 |
6 |
7 |
8 |
9 | Cairo lints
10 |
11 |
12 |
13 | {{ name }}
14 |
15 |
--------------------------------------------------------------------------------
/.github/workflows/on-scarb-new-tag.yml:
--------------------------------------------------------------------------------
1 | name: Respond to New Scarb Tag
2 |
3 | on:
4 | repository_dispatch:
5 | types: [scarb-new-tag-published]
6 |
7 | permissions:
8 | contents: read
9 | pages: write
10 | id-token: write
11 |
12 | jobs:
13 | run-on-tag:
14 | uses: software-mansion/cairo-lint/.github/workflows/website-deploy.yml@main
15 | with:
16 | target_branch: "${{ github.event.client_payload.tag }}"
17 |
--------------------------------------------------------------------------------
/website/docs/lints/bitwise_for_parity_check.md:
--------------------------------------------------------------------------------
1 | # bitwise_for_parity_check
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/bitwise_for_parity_check.rs#L28)
6 |
7 | ## What it does
8 |
9 | Checks for `x & 1` which is unoptimized in cairo and could be replaced by `x % 1`.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let _a = 200_u32 & 1;
16 | }
17 | ```
18 |
--------------------------------------------------------------------------------
/website/docs/lints/double_parens.md:
--------------------------------------------------------------------------------
1 | # double_parens
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/double_parens.rs#L34)
6 |
7 | ## What it does
8 |
9 | Checks for unnecessary double parentheses in expressions.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() -> u32 {
15 | ((0))
16 | }
17 | ```
18 |
19 | Can be simplified to:
20 |
21 | ```cairo
22 | fn main() -> u32 {
23 | 0
24 | }
25 | ```
26 |
--------------------------------------------------------------------------------
/website/docs/lints/div_eq_op.md:
--------------------------------------------------------------------------------
1 | # div_eq_op
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/eq_op.rs#L36)
6 |
7 | ## What it does
8 |
9 | Checks for division with identical operands.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn foo(a: u256) -> u256 {
15 | a / a
16 | }
17 | ```
18 |
19 | Could be simplified by replacing the entire expression with 1:
20 |
21 | ```cairo
22 | fn foo(a: u256) -> u256 {
23 | 1
24 | }
25 | ```
26 |
--------------------------------------------------------------------------------
/website/docs/lints/eq_comp_op.md:
--------------------------------------------------------------------------------
1 | # eq_comp_op
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/eq_op.rs#L72)
6 |
7 | ## What it does
8 |
9 | Checks for comparison with identical operands.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn foo(a: u256) -> bool {
15 | a == a
16 | }
17 | ```
18 |
19 | Could be simplified by replacing the entire expression with true:
20 |
21 | ```cairo
22 | fn foo(a: u256) -> bool {
23 | true
24 | }
25 | ```
26 |
--------------------------------------------------------------------------------
/website/docs/lints/eq_diff_op.md:
--------------------------------------------------------------------------------
1 | # eq_diff_op
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/eq_op.rs#L146)
6 |
7 | ## What it does
8 |
9 | Checks for subtraction with identical operands.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn foo(a: u256) -> u256 {
15 | a - a
16 | }
17 | ```
18 |
19 | Could be simplified by replacing the entire expression with zero:
20 |
21 | ```cairo
22 | fn foo(a: u256) -> u256 {
23 | 0
24 | }
25 | ```
26 |
--------------------------------------------------------------------------------
/website/docs/lints/equatable_if_let.md:
--------------------------------------------------------------------------------
1 | # equatable_if_let
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/ifs/equatable_if_let.rs#L37)
6 |
7 | ## What it does
8 |
9 | Checks for `if let` pattern matching that can be replaced by a simple comparison.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | if let Some(2) = a {
15 | // Code
16 | }
17 | ```
18 |
19 | Could be replaced by
20 |
21 | ```cairo
22 | if a == Some(2) {
23 | // Code
24 | }
25 | ```
26 |
--------------------------------------------------------------------------------
/website/docs/lints/duplicate_underscore_args.md:
--------------------------------------------------------------------------------
1 | # duplicate_underscore_args
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/duplicate_underscore_args.rs#L23)
6 |
7 | ## What it does
8 |
9 | Checks for functions that have the same argument name but prefix with `_`.
10 |
11 | ## Example
12 |
13 | This code will raise a warning because it can be difficult to differentiate between `test` and `_test`.
14 |
15 | ```cairo
16 | fn foo(test: u32, _test: u32) {}
17 | ```
18 |
--------------------------------------------------------------------------------
/website/docs/lints/eq_bitwise_op.md:
--------------------------------------------------------------------------------
1 | # eq_bitwise_op
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/eq_op.rs#L181)
6 |
7 | ## What it does
8 |
9 | Checks for bitwise operation with identical operands.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn foo(a: u256) -> u256 {
15 | a & a
16 | }
17 | ```
18 |
19 | Could be simplified by replacing the entire expression with the operand:
20 |
21 | ```cairo
22 | fn foo(a: u256) -> u256 {
23 | a
24 | }
25 | ```
26 |
--------------------------------------------------------------------------------
/website/docs/lints/eq_logical_op.md:
--------------------------------------------------------------------------------
1 | # eq_logical_op
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/eq_op.rs#L217)
6 |
7 | ## What it does
8 |
9 | Checks for logical operation with identical operands.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn foo(a: u256) -> u256 {
15 | a & a
16 | }
17 | ```
18 |
19 | Could be simplified by replacing the entire expression with the operand:
20 |
21 | ```cairo
22 | fn foo(a: u256) -> u256 {
23 | a
24 | }
25 | ```
26 |
--------------------------------------------------------------------------------
/website/docs/lints/impossible_comparison.md:
--------------------------------------------------------------------------------
1 | # impossible_comparison
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/double_comparison.rs#L38)
6 |
7 | ## What it does
8 |
9 | Checks for impossible comparisons. Those ones always return false.
10 |
11 | ## Example
12 |
13 | Here is an example of impossible comparison:
14 |
15 | ```cairo
16 | fn main() {
17 | let x: u32 = 1;
18 | if x > 200 && x < 100 {
19 | //impossible to reach
20 | }
21 | }
22 | ```
23 |
--------------------------------------------------------------------------------
/website/docs/lints/redundant_op.md:
--------------------------------------------------------------------------------
1 | # redundant_op
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/redundant_op.rs#L36)
6 |
7 | ## What it does
8 |
9 | Checks for redundant arithmetic operations like `x + 0`, `x - 0`, `x * 1`, `x / 1`
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let x = 42;
16 | let _y = x * 1;
17 | }
18 | ```
19 |
20 | Can be simplified to
21 |
22 | ```cairo
23 | fn main() {
24 | let x = 42;
25 | let _y = x;
26 | }
27 | ```
28 |
--------------------------------------------------------------------------------
/website/docs/lints/unit_return_type.md:
--------------------------------------------------------------------------------
1 | # unit_return_type
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/unit_return_type.rs#L38)
6 |
7 | ## What it does
8 |
9 | Detects if the function has a unit return type, which is not needed to be specified.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn foo() -> () {
15 | println!("Hello, world!");
16 | }
17 | ```
18 |
19 | Can be simplified to just:
20 |
21 | ```cairo
22 | fn foo() {
23 | println!("Hello, world!");
24 | }
25 | ```
26 |
--------------------------------------------------------------------------------
/website/docs/lints/break_unit.md:
--------------------------------------------------------------------------------
1 | # break_unit
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/breaks.rs#L39)
6 |
7 | ## What it does
8 |
9 | Checks for `break ();` statements and suggests removing the parentheses.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | loop {
16 | break ();
17 | }
18 | }
19 | ```
20 |
21 | Can be fixed by removing the parentheses:
22 |
23 | ```cairo
24 | fn main() {
25 | loop {
26 | break;
27 | }
28 | }
29 | ```
30 |
--------------------------------------------------------------------------------
/website/docs/lints/manual_unwrap_or.md:
--------------------------------------------------------------------------------
1 | # manual_unwrap_or
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/manual/manual_unwrap_or.rs#L39)
6 |
7 | ## What it does
8 |
9 | Finds patterns that reimplement `Option::unwrap_or` or `Result::unwrap_or`.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | let foo: Option = None;
15 | match foo {
16 | Some(v) => v,
17 | None => 1,
18 | };
19 | ```
20 |
21 | Can be simplified to:
22 |
23 | ```cairo
24 | let foo: Option = None;
25 | foo.unwrap_or(1);
26 | ```
27 |
--------------------------------------------------------------------------------
/website/.vitepress/theme/Layout.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
25 |
--------------------------------------------------------------------------------
/website/docs/lints/int_ge_min_one.md:
--------------------------------------------------------------------------------
1 | # int_ge_min_one
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/int_op_one.rs#L87)
6 |
7 | ## What it does
8 |
9 | Check for unnecessary sub operation in integer >= comparison.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let x: u32 = 1;
16 | let y: u32 = 1;
17 | if x - 1 >= y {}
18 | }
19 | ```
20 |
21 | Can be simplified to:
22 |
23 | ```cairo
24 | fn main() {
25 | let x: u32 = 1;
26 | let y: u32 = 1;
27 | if x > y {}
28 | }
29 | ```
30 |
--------------------------------------------------------------------------------
/website/docs/lints/int_ge_plus_one.md:
--------------------------------------------------------------------------------
1 | # int_ge_plus_one
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/int_op_one.rs#L40)
6 |
7 | ## What it does
8 |
9 | Check for unnecessary add operation in integer >= comparison.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let x: u32 = 1;
16 | let y: u32 = 1;
17 | if x >= y + 1 {}
18 | }
19 | ```
20 |
21 | Can be simplified to:
22 |
23 | ```cairo
24 | fn main() {
25 | let x: u32 = 1;
26 | let y: u32 = 1;
27 | if x > y {}
28 | }
29 | ```
30 |
--------------------------------------------------------------------------------
/website/docs/lints/int_le_min_one.md:
--------------------------------------------------------------------------------
1 | # int_le_min_one
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/int_op_one.rs#L181)
6 |
7 | ## What it does
8 |
9 | Check for unnecessary sub operation in integer <= comparison.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let x: u32 = 1;
16 | let y: u32 = 1;
17 | if x <= y - 1 {}
18 | }
19 | ```
20 |
21 | Can be simplified to:
22 |
23 | ```cairo
24 | fn main() {
25 | let x: u32 = 1;
26 | let y: u32 = 1;
27 | if x < y {}
28 | }
29 | ```
30 |
--------------------------------------------------------------------------------
/website/docs/lints/int_le_plus_one.md:
--------------------------------------------------------------------------------
1 | # int_le_plus_one
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/int_op_one.rs#L134)
6 |
7 | ## What it does
8 |
9 | Check for unnecessary add operation in integer <= comparison.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let x: u32 = 1;
16 | let y: u32 = 1;
17 | if x + 1 <= y {}
18 | }
19 | ```
20 |
21 | Can be simplified to:
22 |
23 | ```cairo
24 | fn main() {
25 | let x: u32 = 1;
26 | let y: u32 = 1;
27 | if x < y {}
28 | }
29 | ```
30 |
--------------------------------------------------------------------------------
/website/docs/lints/empty_enum_brackets_variant.md:
--------------------------------------------------------------------------------
1 | # empty_enum_brackets_variant
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/empty_enum_brackets_variant.rs#L38)
6 |
7 | ## What it does
8 |
9 | Finds enum variants that are declared with empty brackets.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | enum MyEnum {
15 | Data: u8,
16 | Empty: () // redundant parentheses
17 | }
18 | ```
19 |
20 | Can be simplified to:
21 |
22 | ```cairo
23 | enum MyEnum {
24 | Data(u8),
25 | Empty,
26 | }
27 | ```
28 |
--------------------------------------------------------------------------------
/website/docs/lints/equality_match.md:
--------------------------------------------------------------------------------
1 | # equality_match
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/single_match.rs#L86)
6 |
7 | ## What it does
8 |
9 | Checks for matches that do something only in 1 arm and can be rewrote as an `if`
10 |
11 | ## Example
12 |
13 | ```cairo
14 | match variable {
15 | Option::None => println!("None"),
16 | Option::Some => (),
17 | };
18 | ```
19 |
20 | Which can be probably rewritten as
21 |
22 | ```cairo
23 | if variable.is_none() {
24 | println!("None");
25 | }
26 | ```
27 |
--------------------------------------------------------------------------------
/website/docs/lints/destruct_match.md:
--------------------------------------------------------------------------------
1 | # destruct_match
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/single_match.rs#L42)
6 |
7 | ## What it does
8 |
9 | Checks for matches that do something only in 1 arm and can be rewrote as an `if let`
10 |
11 | ## Example
12 |
13 | ```cairo
14 | let var = Option::Some(1_u32);
15 | match var {
16 | Option::Some(val) => do_smth(val),
17 | _ => (),
18 | }
19 | ```
20 |
21 | Which can be rewritten as
22 |
23 | ```cairo
24 | if let Option::Some(val) = var {
25 | do_smth(val),
26 | }
27 | ```
28 |
--------------------------------------------------------------------------------
/website/docs/lints/enum_variant_names.md:
--------------------------------------------------------------------------------
1 | # enum_variant_names
2 |
3 | Default: **Disabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/enum_variant_names.rs#L37)
6 |
7 | ## What it does
8 |
9 | Detects enumeration variants that are prefixed or suffixed by the same characters.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | enum Cake {
15 | BlackForestCake,
16 | HummingbirdCake,
17 | BattenbergCake,
18 | }
19 | ```
20 |
21 | Can be simplified to:
22 |
23 | ```cairo
24 | enum Cake {
25 | BlackForest,
26 | Hummingbird,
27 | Battenberg,
28 | }
29 | ```
30 |
--------------------------------------------------------------------------------
/website/docs/lints/manual_assert.md:
--------------------------------------------------------------------------------
1 | # manual_assert
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/manual/manual_assert.rs#L48)
6 |
7 | ## What it does
8 |
9 | Checks for manual implementations of `assert` macro in `if` expressions.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let a = 5;
16 | if a == 5 {
17 | panic!("a shouldn't be equal to 5");
18 | }
19 | }
20 | ```
21 |
22 | Can be rewritten as:
23 |
24 | ```cairo
25 | fn main() {
26 | let a = 5;
27 | assert!(a != 5, "a shouldn't be equal to 5");
28 | }
29 | ```
30 |
--------------------------------------------------------------------------------
/website/docs/lints/neq_comp_op.md:
--------------------------------------------------------------------------------
1 | # neq_comp_op
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/eq_op.rs#L111)
6 |
7 | ## What it does
8 |
9 | Checks for arithmetical comparison with identical operands.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn foo(a: u256) -> bool {
15 | let _z = a != a;
16 | let _y = a > a;
17 | a < a
18 | }
19 | ```
20 |
21 | Could be simplified by replacing the entire expression with false:
22 |
23 | ```cairo
24 | fn foo(a: u256) -> bool {
25 | let _z = false;
26 | let _y = false;
27 | false
28 | }
29 | ```
30 |
--------------------------------------------------------------------------------
/website/docs/lints/bool_comparison.md:
--------------------------------------------------------------------------------
1 | # bool_comparison
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/bool_comparison.rs#L43)
6 |
7 | ## What it does
8 |
9 | Checks for direct variable with boolean literal like `a == true` or `a == false`.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let x = true;
16 | if x == true {
17 | println!("x is true");
18 | }
19 | }
20 | ```
21 |
22 | Can be rewritten as:
23 |
24 | ```cairo
25 | fn main() {
26 | let x = true;
27 | if x {
28 | println!("x is true");
29 | }
30 | }
31 | ```
32 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # Please see the documentation for all configuration options:
2 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
3 |
4 | version: 2
5 | updates:
6 | - package-ecosystem: "cargo"
7 | directory: "/"
8 | schedule:
9 | interval: "monthly"
10 | groups:
11 | cairo:
12 | patterns:
13 | - "cairo-lang-*"
14 | non critical:
15 | patterns:
16 | - "*"
17 | exclude-patterns:
18 | - "cairo-lang-*"
19 |
20 | - package-ecosystem: "github-actions"
21 | directory: "/"
22 | schedule:
23 | interval: "monthly"
24 |
--------------------------------------------------------------------------------
/website/docs/lints/inefficient_while_comp.md:
--------------------------------------------------------------------------------
1 | # inefficient_while_comp
2 |
3 | Default: **Disabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/performance/inefficient_while_comp.rs#L37)
6 |
7 | ## What it does
8 |
9 | Checks if the while loop exit condition is using [`<`, `<=`, `>=`, `>`] operators.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let mut a = 1_u32;
16 | while a <= 10 {
17 | a += 1;
18 | }
19 | }
20 | ```
21 |
22 | Can be optimized to:
23 |
24 | ```cairo
25 | fn main() {
26 | let mut a = 1_u32;
27 | while a != 10 {
28 | a += 1;
29 | }
30 | }
31 | ```
32 |
--------------------------------------------------------------------------------
/website/docs/lints/manual_is_none.md:
--------------------------------------------------------------------------------
1 | # manual_is_none
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/manual/manual_is.rs#L89)
6 |
7 | ## What it does
8 |
9 | Checks for manual implementations of `is_none`.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let foo: Option = Option::None;
16 | let _foo = match foo {
17 | Option::Some(_) => false,
18 | Option::None => true,
19 | };
20 | }
21 | ```
22 |
23 | Can be rewritten as:
24 |
25 | ```cairo
26 | fn main() {
27 | let foo: Option = Option::None;
28 | let _foo = foo.is_none();
29 | }
30 | ```
31 |
--------------------------------------------------------------------------------
/website/docs/lints/manual_is_some.md:
--------------------------------------------------------------------------------
1 | # manual_is_some
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/manual/manual_is.rs#L41)
6 |
7 | ## What it does
8 |
9 | Checks for manual implementations of `is_some`.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let foo: Option = Option::None;
16 | let _foo = match foo {
17 | Option::Some(_) => true,
18 | Option::None => false,
19 | };
20 | }
21 | ```
22 |
23 | Can be rewritten as:
24 |
25 | ```cairo
26 | fn main() {
27 | let foo: Option = Option::None;
28 | let _foo = foo.is_some();
29 | }
30 | ```
31 |
--------------------------------------------------------------------------------
/website/docs/lints/manual_is_ok.md:
--------------------------------------------------------------------------------
1 | # manual_is_ok
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/manual/manual_is.rs#L137)
6 |
7 | ## What it does
8 |
9 | Checks for manual implementations of `is_ok`.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let res_val: Result = Result::Err('err');
16 | let _a = match res_val {
17 | Result::Ok(_) => true,
18 | Result::Err(_) => false
19 | };
20 | }
21 | ```
22 |
23 | Can be rewritten as:
24 |
25 | ```cairo
26 | fn main() {
27 | let res_val: Result = Result::Err('err');
28 | let _a = res_val.is_ok();
29 | }
30 | ```
31 |
--------------------------------------------------------------------------------
/website/docs/lints/manual_is_err.md:
--------------------------------------------------------------------------------
1 | # manual_is_err
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/manual/manual_is.rs#L185)
6 |
7 | ## What it does
8 |
9 | Checks for manual implementations of `is_err`.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let res_val: Result = Result::Err('err');
16 | let _a = match res_val {
17 | Result::Ok(_) => false,
18 | Result::Err(_) => true
19 | };
20 | }
21 | ```
22 |
23 | Can be rewritten as:
24 |
25 | ```cairo
26 | fn main() {
27 | let res_val: Result = Result::Err('err');
28 | let _a = res_val.is_err();
29 | }
30 | ```
31 |
--------------------------------------------------------------------------------
/website/docs/lints/manual_unwrap_or_default.md:
--------------------------------------------------------------------------------
1 | # manual_unwrap_or_default
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/manual/manual_unwrap_or_default.rs#L48)
6 |
7 | ## What it does
8 |
9 | Checks for manual unwrapping of an Option or Result.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let x: Option = Option::Some(1038);
16 | if let Option::Some(v) = x {
17 | v
18 | } else {
19 | 0
20 | };
21 | }
22 | ```
23 |
24 | Can be simplified to:
25 |
26 | ```cairo
27 | fn main() {
28 | let x: Option = Option::Some(1038);
29 | x.unwrap_or_default();
30 | }
31 | ```
32 |
--------------------------------------------------------------------------------
/website/docs/lints/erasing_op.md:
--------------------------------------------------------------------------------
1 | # erasing_op
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/erasing_op.rs#L41)
6 |
7 | ## What it does
8 |
9 | Checks for operations that result in the value being erased (e.g., multiplication by 0 or 0 being divided by anything).
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let x = 1;
16 | let _y = 0 * x;
17 | let _z = 0 / x;
18 | let _c = x & 0;
19 | }
20 | ```
21 |
22 | Could be simplified by replacing the entire expression with 0:
23 |
24 | ```cairo
25 | fn main() {
26 | let x = 1;
27 | let _y = 0;
28 | let _z = 0;
29 | let _c = 0;
30 | }
31 | ```
32 |
--------------------------------------------------------------------------------
/website/docs/lints/manual_expect.md:
--------------------------------------------------------------------------------
1 | # manual_expect
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/manual/manual_expect.rs#L46)
6 |
7 | ## What it does
8 |
9 | Checks for manual implementations of `expect`.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let foo: Option:: = Option::None;
16 | let _foo = match foo {
17 | Option::Some(x) => x,
18 | Option::None => core::panic_with_felt252('err'),
19 | };
20 | }
21 | ```
22 |
23 | Can be rewritten as:
24 |
25 | ```cairo
26 | fn main() {
27 | let foo: Option:: = Option::None;
28 | let _foo = foo.expect('err');
29 | }
30 | ```
31 |
--------------------------------------------------------------------------------
/website/docs/lints/manual_err.md:
--------------------------------------------------------------------------------
1 | # manual_err
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/manual/manual_err.rs#L41)
6 |
7 | ## What it does
8 |
9 | Checks for manual implementations of `err` in match and if expressions.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let foo: Result = Result::Err('err');
16 | let _foo = match foo {
17 | Result::Ok(_) => Option::None,
18 | Result::Err(x) => Option::Some(x),
19 | };
20 | }
21 | ```
22 |
23 | Can be rewritten as:
24 |
25 | ```cairo
26 | fn main() {
27 | let foo: Result = Result::Err('err');
28 | let _foo = foo.err();
29 | }
30 | ```
31 |
--------------------------------------------------------------------------------
/website/docs/lints/manual_ok_or.md:
--------------------------------------------------------------------------------
1 | # manual_ok_or
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/manual/manual_ok_or.rs#L52)
6 |
7 | ## What it does
8 |
9 | Checks for manual implementations of ok_or.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let foo: Option = Option::None;
16 | let _foo = match foo {
17 | Option::Some(v) => Result::Ok(v),
18 | Option::None => Result::Err('this is an err'),
19 | };
20 | }
21 | ```
22 |
23 | Can be rewritten as:
24 |
25 | ```cairo
26 | fn main() {
27 | let foo: Option = Option::None;
28 | let _foo = foo.ok_or('this is an err');
29 | }
30 | ```
31 |
--------------------------------------------------------------------------------
/website/docs/lints/redundant_brackets_in_enum_call.md:
--------------------------------------------------------------------------------
1 | # redundant_brackets_in_enum_call
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/redundant_brackets_in_enum_call.rs#L47)
6 |
7 | ## What it does
8 |
9 | Detects calls to enum variant constructors with redundant parentheses
10 |
11 | ## Example
12 |
13 | ```cairo
14 | enum MyEnum {
15 | Data: u8,
16 | Empty,
17 | }
18 |
19 | fn main() {
20 | let a = MyEnum::Empty(()); // redundant parentheses
21 | }
22 | ```
23 |
24 | Can be simplified to:
25 |
26 | ```cairo
27 | enum MyEnum {
28 | Data: u8,
29 | Empty,
30 | }
31 |
32 | fn main() {
33 | let a = MyEnum::Empty;
34 | }
35 | ```
36 |
--------------------------------------------------------------------------------
/website/docs/lints/loop_match_pop_front.md:
--------------------------------------------------------------------------------
1 | # loop_match_pop_front
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/loops/loop_match_pop_front.rs#L53)
6 |
7 | ## What it does
8 |
9 | Checks for loops that are used to iterate over a span using `pop_front`.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | let a: Span = array![1, 2, 3].span();
15 | loop {
16 | match a.pop_front() {
17 | Option::Some(val) => {do_smth(val); },
18 | Option::None => { break; }
19 | }
20 | }
21 | ```
22 |
23 | Which can be rewritten as
24 |
25 | ```cairo
26 | let a: Span = array![1, 2, 3].span();
27 | for val in a {
28 | do_smth(val);
29 | }
30 | ```
31 |
--------------------------------------------------------------------------------
/website/docs/lints/manual_ok.md:
--------------------------------------------------------------------------------
1 | # manual_ok
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/manual/manual_ok.rs#L41)
6 |
7 | ## What it does
8 |
9 | Checks for manual implementation of `ok` method in match and if expressions.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let res_val: Result = Result::Err('err');
16 | let _a = match res_val {
17 | Result::Ok(x) => Option::Some(x),
18 | Result::Err(_) => Option::None,
19 | };
20 | }
21 | ```
22 |
23 | Can be replaced with:
24 |
25 | ```cairo
26 | fn main() {
27 | let res_val: Result = Result::Err('err');
28 | let _a = res_val.ok();
29 | }
30 | ```
31 |
--------------------------------------------------------------------------------
/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cairo-lint-website",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vitepress dev",
7 | "build": "vitepress build",
8 | "clean": "rm -rf .vitepress/cache .vitepress/dist",
9 | "fmt": "prettier --write .",
10 | "fmt:check": "prettier --check .",
11 | "preview": "vitepress preview"
12 | },
13 | "dependencies": {
14 | "cairo-tm-grammar": "github:software-mansion-labs/cairo-tm-grammar",
15 | "prettier": "3.3.2",
16 | "vitepress": "1.2.3",
17 | "vue": "3.4.30"
18 | },
19 | "optionalDependencies": {
20 | "@rollup/rollup-linux-x64-gnu": "4.9.5"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/website/docs/lints/contradictory_comparison.md:
--------------------------------------------------------------------------------
1 | # contradictory_comparison
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/double_comparison.rs#L188)
6 |
7 | ## What it does
8 |
9 | Checks for double comparisons that are contradictory. Those are comparisons that are always false.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() -> bool {
15 | let x = 5_u32;
16 | let y = 10_u32;
17 | if x < y && x > y {
18 | true
19 | } else {
20 | false
21 | }
22 | }
23 | ```
24 |
25 | Could be simplified to just:
26 |
27 | ```cairo
28 | fn main() -> bool {
29 | let x = 5_u32;
30 | let y = 10_u32;
31 | false
32 | }
33 | ```
34 |
--------------------------------------------------------------------------------
/website/docs/lints/redundant_comparison.md:
--------------------------------------------------------------------------------
1 | # redundant_comparison
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/double_comparison.rs#L137)
6 |
7 | ## What it does
8 |
9 | Checks for double comparisons that are redundant. Those are comparisons that can be simplified to a single comparison.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() -> bool {
15 | let x = 5_u32;
16 | let y = 10_u32;
17 | if x >= y || x <= y {
18 | true
19 | } else {
20 | false
21 | }
22 | }
23 | ```
24 |
25 | Could be simplified to just:
26 |
27 | ```cairo
28 | fn main() -> bool {
29 | let x = 5_u32;
30 | let y = 10_u32;
31 | true
32 | }
33 | ```
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Cairo lint
2 |
3 | A collection of lints to catch common mistakes and improve your [Cairo](https://github.com/starkware-libs/cairo) code.
4 |
5 | ## Quick start
6 |
7 | Cairo-lint is a library that can detect and fix the detected problems.
8 |
9 | It it also shipped as a CLI with Scarb. To learn more visit [scarb lint](https://docs.swmansion.com/scarb/docs/extensions/linter.html) docs.
10 |
11 | ## Features
12 |
13 | - The `--test` flag to include test files.
14 |
15 | ## Documentation
16 |
17 | All information about Cairo lint is available on project's [website](https://docs.swmansion.com/cairo-lint/).
18 |
19 | ## Community
20 |
21 | As for now there is only a [telegram channel](https://t.me/cairolint) dedicated to cairo-lint.
22 |
--------------------------------------------------------------------------------
/website/docs/lints/ifs_same_cond.md:
--------------------------------------------------------------------------------
1 | # ifs_same_cond
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/ifs/ifs_same_cond.rs#L43)
6 |
7 | ## What it does
8 |
9 | Checks for consecutive `if` expressions with the same condition.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let a = 1;
16 | let b = 1;
17 | if a == b {
18 | println!("a is equal to b");
19 | } else if a == b {
20 | println!("a is equal to b");
21 | }
22 | }
23 | ```
24 |
25 | Could be rewritten as just:
26 |
27 | ```cairo
28 | fn main() {
29 | let a = 1;
30 | let b = 1;
31 | if a == b {
32 | println!("a is equal to b");
33 | }
34 | }
35 | ```
36 |
--------------------------------------------------------------------------------
/website/docs/lints/loop_for_while.md:
--------------------------------------------------------------------------------
1 | # loop_for_while
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/loops/loop_for_while.rs#L49)
6 |
7 | ## What it does
8 |
9 | Checks for `loop` expressions that contain a conditional `if` statement with break inside that
10 | can be simplified to a `while` loop.
11 |
12 | ## Example
13 |
14 | ```cairo
15 | fn main() {
16 | let mut x: u16 = 0;
17 | loop {
18 | if x == 10 {
19 | break;
20 | }
21 | x += 1;
22 | }
23 | }
24 | ```
25 |
26 | Can be simplified to:
27 |
28 | ```cairo
29 | fn main() {
30 | let mut x: u16 = 0;
31 | while x != 10 {
32 | x += 1;
33 | }
34 | }
35 | ```
36 |
--------------------------------------------------------------------------------
/website/.vitepress/components/home/Download.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Installation
4 |
5 | Cairo Lint comes included with the Scarb, so you can just install it
6 | here.
12 |
13 |
14 |
15 |
16 |
35 |
--------------------------------------------------------------------------------
/website/docs/lints/collapsible_if.md:
--------------------------------------------------------------------------------
1 | # collapsible_if
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/ifs/collapsible_if.rs#L52)
6 |
7 | ## What it does
8 |
9 | Checks for nested `if` statements that can be collapsed into a single `if` statement.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let x = true;
16 | let y = true;
17 | let z = false;
18 |
19 | if x || z {
20 | if y && z {
21 | println!("Hello");
22 | }
23 | }
24 | }
25 | ```
26 |
27 | Can be collapsed to
28 |
29 | ```cairo
30 | fn main() {
31 | let x = true;
32 | let y = true;
33 | let z = false;
34 | if (x || z) && (y && z) {
35 | println!("Hello");
36 | }
37 | }
38 | ```
39 |
--------------------------------------------------------------------------------
/website/docs/lints/manual_expect_err.md:
--------------------------------------------------------------------------------
1 | # manual_expect_err
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/manual/manual_expect_err.rs#L54)
6 |
7 | ## What it does
8 |
9 | Checks for manual implementation of `expect_err` method in match and if expressions.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | fn main() {
15 | let foo: Result = Result::Err('err');
16 | let err = 'this is an err';
17 | let _foo = match foo {
18 | Result::Ok(_) => core::panic_with_felt252(err),
19 | Result::Err(x) => x,
20 | };
21 | }
22 | ```
23 |
24 | Can be rewritten as:
25 |
26 | ```cairo
27 | fn main() {
28 | let foo: Result = Result::Err('err');
29 | let err = 'this is an err';
30 | let _foo = foo.expect_err(err);
31 | }
32 | ```
33 |
--------------------------------------------------------------------------------
/website/docs/lints/simplifiable_comparison.md:
--------------------------------------------------------------------------------
1 | # simplifiable_comparison
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/double_comparison.rs#L86)
6 |
7 | ## What it does
8 |
9 | Checks for double comparisons that can be simplified.
10 | Those are comparisons that can be simplified to a single comparison.
11 |
12 | ## Example
13 |
14 | ```cairo
15 | fn main() -> bool {
16 | let x = 5_u32;
17 | let y = 10_u32;
18 | if x == y || x > y {
19 | true
20 | } else {
21 | false
22 | }
23 | }
24 | ```
25 |
26 | The above code can be simplified to:
27 |
28 | ```cairo
29 | fn main() -> bool {
30 | let x = 5_u32;
31 | let y = 10_u32;
32 | if x >= y {
33 | true
34 | } else {
35 | false
36 | }
37 | }
38 | ```
39 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Only the latest release is supported with security updates.
6 |
7 | ## Reporting a Vulnerability
8 |
9 | If there are any vulnerabilities in **Cairo language linter**, don't hesitate to
10 | _report them_.
11 |
12 | 1. If you found a vulnerability in **Scarb**, please consult its
13 | own [security policy](https://github.com/software-mansion/scarb/security/policy).
14 | 2. Use the GitHub Security site for reporting vulnerabilities. You can report
15 | one [here](https://github.com/software-mansion/cairo-lint/security/advisories/new).
16 | 3. Please **do not disclose the vulnerability publicly** until a fix is released!
17 | 4. Once we have either: a) published a fix or b) declined to address the vulnerability for whatever
18 | reason, you are free to publicly disclose it.
19 |
--------------------------------------------------------------------------------
/website/docs/lints/collapsible_if_else.md:
--------------------------------------------------------------------------------
1 | # collapsible_if_else
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/ifs/collapsible_if_else.rs#L54)
6 |
7 | ## What it does
8 |
9 | Checks for nested `if` statements inside the `else` statement
10 | that can be collapsed into a single `if-else` statement.
11 |
12 | ## Example
13 |
14 | ```cairo
15 | fn main() {
16 | let x = true;
17 | if x {
18 | println!("x is true");
19 | } else {
20 | if !x {
21 | println!("x is false");
22 | }
23 | }
24 | }
25 | ```
26 |
27 | Can be refactored to:
28 |
29 | ```cairo
30 | fn main() {
31 | let x = true;
32 | if x {
33 | println!("x is true");
34 | } else if !x {
35 | println!("x is false");
36 | }
37 | }
38 | ```
39 |
--------------------------------------------------------------------------------
/xtask/src/main.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use clap::Parser;
3 |
4 | macro_rules! command {
5 | ($enum_name:ident ( $($module:ident,)+ )) => {
6 | $(mod $module;)+
7 |
8 | #[derive(::clap::Subcommand)]
9 | #[allow(non_camel_case_types)]
10 | enum $enum_name {
11 | $($module(crate::$module::Args),)+
12 | }
13 |
14 | impl $enum_name {
15 | fn main(self) -> ::anyhow::Result<()> {
16 | match self {
17 | $(Self::$module(args) => crate::$module::main(args),)+
18 | }
19 | }
20 | }
21 | }
22 | }
23 |
24 | command!(Command(upgrade, sync_version, update_docs,));
25 |
26 | #[derive(Parser)]
27 | struct Args {
28 | #[command(subcommand)]
29 | command: Command,
30 | }
31 |
32 | fn main() -> Result<()> {
33 | Args::parse().command.main()
34 | }
35 |
--------------------------------------------------------------------------------
/tests/main.rs:
--------------------------------------------------------------------------------
1 | mod bitwise_for_parity_check;
2 | mod bool_comparison;
3 | mod breaks;
4 | mod clone_on_copy;
5 | mod collapsible_match;
6 | mod double_comparison;
7 | mod double_parens;
8 | mod duplicate_underscore_args;
9 | mod empty_enum_brackets_variant;
10 | mod enum_variant_names;
11 | mod eq_op;
12 | mod erasing_operations;
13 | mod fix_messages;
14 | mod helpers;
15 | mod ifs;
16 | mod int_operations;
17 | mod loops;
18 | mod manual;
19 | mod nested_fixes;
20 | mod panic;
21 | mod performance;
22 | mod redundant_brackets_in_enum_call;
23 | mod redundant_into;
24 | mod redundant_op;
25 | mod single_match;
26 | mod unit_return_type;
27 | mod unused_imports;
28 | mod unused_variables;
29 | mod unwrap_syscall;
30 |
31 | pub const CRATE_CONFIG: &str = r#"
32 | edition = "2024_07"
33 |
34 | [experimental_features]
35 | negative_impls = true
36 | coupons = true
37 | associated_item_constraints = true
38 | user_defined_inline_macros = true
39 | "#;
40 |
--------------------------------------------------------------------------------
/website/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/.gitignore:
--------------------------------------------------------------------------------
1 | *.lcov
2 | *.log
3 | *.pid
4 | *.pid.lock
5 | *.seed
6 | *.tgz
7 | *.tsbuildinfo
8 | .cache
9 | .cache/
10 | .docusaurus
11 | .dynamodb/
12 | .env
13 | .env.development.local
14 | .env.local
15 | .env.production.local
16 | .env.test.local
17 | .eslintcache
18 | .fusebox/
19 | .grunt
20 | .lock-wscript
21 | .next
22 | .node_repl_history
23 | .npm
24 | .nuxt
25 | .nyc_output
26 | .parcel-cache
27 | .pnp.*
28 | .pnpm-debug.log*
29 | .rpt2_cache/
30 | .rts2_cache_cjs/
31 | .rts2_cache_es/
32 | .rts2_cache_umd/
33 | .serverless/
34 | .stylelintcache
35 | .temp
36 | .tern-port
37 | .vitepress/cache
38 | .vscode-test
39 | .vuepress/dist
40 | .yarn-integrity
41 | .yarn/build-state.yml
42 | .yarn/cache
43 | .yarn/install-state.gz
44 | .yarn/unplugged
45 | bower_components
46 | build/Release
47 | coverage
48 | dist
49 | jspm_packages/
50 | lerna-debug.log*
51 | lib-cov
52 | logs
53 | node_modules/
54 | npm-debug.log*
55 | out
56 | pids
57 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
58 | web_modules/
59 | yarn-debug.log*
60 | yarn-error.log*
61 |
--------------------------------------------------------------------------------
/MAINTAINING.md:
--------------------------------------------------------------------------------
1 | # Cairo-lint Maintenance
2 |
3 | ## Release procedure
4 |
5 | To release a new version of `cairo-lint`:
6 |
7 | 1. Bump `cairo-lint` version in `Cargo.toml` file.
8 | 2. Make sure all the `cairo-lang-*` dependencies are set to a version appropriate for your release.
9 | You can use the following command:
10 | ```shell
11 | cargo xtask upgrade cairo VERSION
12 | ```
13 | where `VERSION` is the appropriate version.
14 |
15 | The `patch` section in `Cargo.toml` should be **empty** after doing it.
16 | 3. Push the changes, create a PR and verify if the CI passes.
17 | 4. If releasing for the first time, run:
18 | ```shell
19 | cargo login
20 | ```
21 | and follow terminal prompts to generate a token with at least `publish-update` permission.
22 | 5. Run
23 | ```shell
24 | cargo publish
25 | ```
26 | OR (if using multiple tokens for multiple crates):
27 | ```shell
28 | cargo publish --token
29 | ```
30 | to publish the package.
31 | 6. Create a tag corresponding to the release. The naming convention is `v{VERSION}` e.g. `v2.12.0`.
32 |
--------------------------------------------------------------------------------
/src/plugin.rs:
--------------------------------------------------------------------------------
1 | use cairo_lang_defs::ids::ModuleId;
2 | use cairo_lang_defs::plugin::PluginDiagnostic;
3 | use cairo_lang_semantic::plugin::{AnalyzerPlugin, PluginSuite};
4 | use salsa::Database;
5 |
6 | use crate::context::get_unique_allowed_names;
7 |
8 | pub fn cairo_lint_allow_plugin_suite() -> PluginSuite {
9 | let mut suite = PluginSuite::default();
10 | suite.add_analyzer_plugin::();
11 | suite
12 | }
13 |
14 | /// Plugin with `declared_allows` that does not emit diagnostics.
15 | /// Add it to avoid compiler warnings on unsupported `allow` attribute arguments.
16 | #[derive(Debug, Default)]
17 | pub struct CairoLintAllow;
18 |
19 | impl AnalyzerPlugin for CairoLintAllow {
20 | fn diagnostics<'db>(
21 | &self,
22 | _db: &'db dyn Database,
23 | _module_id: ModuleId<'db>,
24 | ) -> Vec> {
25 | Vec::new()
26 | }
27 |
28 | fn declared_allows(&self) -> Vec {
29 | get_unique_allowed_names()
30 | .iter()
31 | .map(ToString::to_string)
32 | .collect()
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: Feature Request
2 | description: Suggest an idea for enhancing Cairo language linter.
3 | type: Feature
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | Thanks for filing a 🙋 feature request! 😄
9 |
10 | In case of a relatively big feature that needs feedback from the community, [the cairo-lint Telegram channel][tg] is the best fit.
11 |
12 | [tg]: https://t.me/cairolint
13 | - type: textarea
14 | id: problem
15 | attributes:
16 | label: Problem
17 | description: >
18 | Please provide a clear description of your use case and the problem
19 | this feature request is trying to solve.
20 | validations:
21 | required: true
22 | - type: textarea
23 | id: solution
24 | attributes:
25 | label: Proposed Solution
26 | description: >
27 | Please provide a clear and concise description of what you want to happen.
28 | - type: textarea
29 | id: notes
30 | attributes:
31 | label: Notes
32 | description: Provide any additional context or information that might be helpful.
33 |
--------------------------------------------------------------------------------
/website/docs/lints/unwrap_syscall.md:
--------------------------------------------------------------------------------
1 | # unwrap_syscall
2 |
3 | Default: **Enabled**
4 |
5 | [Source Code](https://github.com/software-mansion/cairo-lint/tree/main/src/lints/unwrap_syscall.rs#L52)
6 |
7 | ## What it does
8 |
9 | Detects if the function uses `unwrap` on a `SyscallResult` object.
10 |
11 | ## Example
12 |
13 | ```cairo
14 | use starknet::storage_access::{storage_address_from_base, storage_base_address_from_felt252};
15 | use starknet::syscalls::storage_read_syscall;
16 |
17 | fn main() {
18 | let storage_address = storage_base_address_from_felt252(3534535754756246375475423547453);
19 | let result = storage_read_syscall(0, storage_address_from_base(storage_address));
20 | result.unwrap();
21 | }
22 | ```
23 |
24 | Can be changed to:
25 |
26 | ```cairo
27 | use starknet::SyscallResultTrait;
28 | use starknet::storage_access::{storage_address_from_base, storage_base_address_from_felt252};
29 | use starknet::syscalls::storage_read_syscall;
30 |
31 | fn main() {
32 | let storage_address = storage_base_address_from_felt252(3534535754756246375475423547453);
33 | let result = storage_read_syscall(0, storage_address_from_base(storage_address));
34 | result.unwrap_syscall();
35 | }
36 | ```
37 |
--------------------------------------------------------------------------------
/tests/fix_messages/mod.rs:
--------------------------------------------------------------------------------
1 | use cairo_lint::context::get_all_fix_messages;
2 |
3 | #[test]
4 | fn check_fix_message() {
5 | let fix_messages = get_all_fix_messages();
6 |
7 | // Ensure all lints with a fixer have a non-empty fix_message
8 | for msg in fix_messages {
9 | assert!(
10 | msg.is_some(),
11 | "Every lint with `has_fixer` must provide a `fix_message`"
12 | );
13 | assert!(
14 | !msg.unwrap().is_empty(),
15 | "`fix_message` should not be an empty string"
16 | );
17 | }
18 | }
19 |
20 | #[test]
21 | #[should_panic(expected = "`fix_message` should not be an empty string")]
22 | fn test_empty_fix_message_panics() {
23 | let mut fix_messages = get_all_fix_messages();
24 |
25 | // Simulate a lint with an empty fix_message (for testing purposes)
26 | let empty_fix_message: Option<&'static str> = Some("");
27 | fix_messages.push(empty_fix_message);
28 |
29 | // Trigger the assertion that checks for non-empty messages
30 | for msg in fix_messages {
31 | assert!(
32 | msg.is_some(),
33 | "Every lint with `has_fixer` must provide a `fix_message`"
34 | );
35 | assert!(
36 | !msg.unwrap().is_empty(),
37 | "`fix_message` should not be an empty string"
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/.github/workflows/nightly.yml:
--------------------------------------------------------------------------------
1 | name: Nightly
2 |
3 | on:
4 | schedule:
5 | - cron: "0 0 * * 1,3,5"
6 | workflow_dispatch:
7 |
8 | permissions:
9 | contents: read
10 |
11 | jobs:
12 | test-with-nightly-cairo:
13 | name: Test nightly
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@v6
17 | - uses: dtolnay/rust-toolchain@stable
18 |
19 | - name: Upgrade Cairo to latest main commit
20 | run: cargo xtask upgrade cairo --rev $(git ls-remote --refs "https://github.com/starkware-libs/cairo" main | awk '{print $1}')
21 | - name: Get corelib
22 | run: git clone https://github.com/starkware-libs/cairo
23 | - name: Run cargo test
24 | run: CORELIB_PATH="$(pwd)/cairo/corelib/src" cargo test
25 |
26 | notify_failed:
27 | runs-on: ubuntu-latest
28 | if: always() && needs.test-with-nightly-cairo.result == 'failure' && github.event_name == 'schedule'
29 | needs: test-with-nightly-cairo
30 | steps:
31 | - name: Notify the team about workflow failure
32 | uses: slackapi/slack-github-action@v2.1.1
33 | with:
34 | webhook: ${{ secrets.SLACK_NIGHTLY_CHECK_FAILURE_WEBHOOK_URL }}
35 | webhook-type: webhook-trigger
36 | payload: |
37 | url: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
38 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | description: Create a report to help us improve
3 | type: Bug
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: Thanks for filing a 🐛 bug report 😄!
8 | - type: textarea
9 | id: problem
10 | attributes:
11 | label: Problem
12 | description: >
13 | Please provide a clear and concise description of what the bug is,
14 | including what currently happens and what you expected to happen.
15 | validations:
16 | required: true
17 | - type: textarea
18 | id: steps
19 | attributes:
20 | label: Steps
21 | description: Please list the steps to reproduce the bug.
22 | placeholder: |
23 | 1.
24 | 2.
25 | 3.
26 | - type: textarea
27 | id: scarb-version
28 | attributes:
29 | label: Scarb Version
30 | description: >
31 | Please paste the output of running `scarb --version`.
32 | Remember to do this in the affected project directory.
33 | render: text
34 | validations:
35 | required: true
36 | - type: textarea
37 | id: possible-solutions
38 | attributes:
39 | label: Possible Solution(s)
40 | description: >
41 | Not obligatory, but suggest a fix/reason for the bug,
42 | or ideas how to implement the addition or change.
43 | - type: textarea
44 | id: notes
45 | attributes:
46 | label: Notes
47 | description: Provide any additional notes that might be helpful.
48 |
--------------------------------------------------------------------------------
/ROADMAP.md:
--------------------------------------------------------------------------------
1 | # Cairo Lint Roadmap
2 |
3 | A list of main features and tasks that are planned to be added for Cairo lint.
4 |
5 | The list is sorted from top to bottom by the priority that each task has. The higher the priority, the higher the place of the task in the list.
6 | Size of the task is ranked `1-5`, where `1` is smallest and `5` biggest.
7 |
8 | For more detailed info, look into the [Board](https://github.com/orgs/software-mansion/projects/33/views/7) into the `Backlog Lint` and `Todo` sections.
9 |
10 | ## Q4 2025 and Q1 2026
11 |
12 | ### 1. Fixing any existing bugs
13 |
14 | We still have some bugs to be addressed. Most of them are related to code produced by inline macros being considered as a user code. Also other minor bugs related to lints such as `unwrap_or_else`.
15 |
16 | Size: 4
17 |
18 | ### 2. Upstreaming shared logic with Cairo Language Server
19 |
20 | A lot of the critical code parts are just blatantly copied from Language Server. We want to create a separate crate that will contain all the shared logic between those two. This way we get a single source of truth, and it will be much easier to maintain.
21 |
22 | Size: 3
23 |
24 | ### 3. Add various new lints and fixers
25 |
26 | We want to add new lint rules (and corresponding fixer if possible), such as `collapsible_match`, `disallowed_methods`, `inefficient_unwrap_or`, `impossible_comparison` and much more. The list of lints waiting to be implemented is very long, but those mentioned should be the priority during this period.
27 |
28 | Size: 5
29 |
--------------------------------------------------------------------------------
/tests/helpers/setup.rs:
--------------------------------------------------------------------------------
1 | use cairo_lang_filesystem::db::{CrateConfigurationInput, CrateSettings, files_group_input};
2 | use cairo_lang_filesystem::{
3 | db::{Edition, ExperimentalFeaturesConfig},
4 | ids::FileKind,
5 | };
6 | use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
7 | use std::collections::BTreeMap;
8 |
9 | use crate::CRATE_CONFIG;
10 | use cairo_lang_filesystem::ids::{CrateInput, DirectoryInput, FileInput, VirtualFileInput};
11 | use cairo_lint::LinterAnalysisDatabase;
12 | use salsa::Setter;
13 |
14 | pub fn setup_test_crate_ex(db: &mut LinterAnalysisDatabase, content: &str) -> CrateInput {
15 | let settings = CrateSettings {
16 | name: None,
17 | edition: Edition::latest(),
18 | version: None,
19 | dependencies: Default::default(),
20 | experimental_features: ExperimentalFeaturesConfig {
21 | negative_impls: true,
22 | associated_item_constraints: true,
23 | coupons: true,
24 | user_defined_inline_macros: true,
25 | repr_ptrs: true,
26 | },
27 | cfg_set: Default::default(),
28 | };
29 | let file = FileInput::Virtual(VirtualFileInput {
30 | parent: None,
31 | name: "lib.cairo".into(),
32 | content: content.into(),
33 | code_mappings: [].into(),
34 | kind: FileKind::Module,
35 | original_item_removed: false,
36 | });
37 |
38 | let cr = CrateInput::Virtual {
39 | name: "test".into(),
40 | file_long_id: file.clone(),
41 | settings: CRATE_CONFIG.to_string(),
42 | cache_file: None,
43 | };
44 |
45 | files_group_input(db)
46 | .set_crate_configs(db)
47 | .to(Some(OrderedHashMap::from([(
48 | cr.clone(),
49 | CrateConfigurationInput {
50 | root: DirectoryInput::Virtual {
51 | files: BTreeMap::from([("lib.cairo".to_string(), file)]),
52 | dirs: Default::default(),
53 | },
54 | settings,
55 | cache_file: None,
56 | },
57 | )])));
58 |
59 | cr
60 | }
61 |
--------------------------------------------------------------------------------
/website/docs.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | Cairo lint is a static code analysis tool for the Cairo language.
4 |
5 | It can help you improve your code quality and consistency by checking the codebase against a set of predefined rules, called lints.
6 | It can also automatically fix some of the issues found.
7 |
8 | This tool is mostly depended on the separate lint rules. You can also read about every each of them here in the `Lints` section of the documentation.
9 |
10 | ## Installation
11 |
12 | Cairo lint is provided within the [Scarb](https://docs.swmansion.com/scarb/) toolchain. You can install and download it [here](https://docs.swmansion.com/scarb/download.html)
13 |
14 | ## Getting started
15 |
16 | To run Cairo lint in the current project, just type:
17 |
18 | ```sh
19 | scarb lint
20 | ```
21 |
22 | This will run the code analysis and suggest places to edit your code.
23 | Running `lint` will yield issues like this:
24 |
25 | ```sh
26 | $ scarb lint
27 | Linting hello_world v0.1.0 (/hello_world/Scarb.toml)
28 | warning: Plugin diagnostic: Unnecessary comparison with a boolean value. Use the variable directly.
29 | --> /hello_world/src/lib.cairo:2:8
30 | |
31 | 2 | if is_true() == true {
32 | | -----------------
33 | |
34 | ```
35 |
36 | To attempt to fix the issues automatically, you can run:
37 |
38 | ```sh
39 | scarb lint --fix
40 | ```
41 |
42 | You can also specify `--test` to perform analysis of your project's tests as well (i.e. all the Cairo code under `#[cfg(test)]` attributes).
43 | To learn more about available arguments, just run `scarb lint --help`.
44 |
45 | ## Configuration
46 |
47 | By default, most of the lints are enabled and checked, but some of them are disabled as they are much more pedantic. You can check whether a certain lint is enabled by default in the documentation. To adjust this configuration, you can manually set those values in your `Scarb.toml` as follows:
48 |
49 | ```toml
50 | [tool.cairo-lint]
51 | panic = true
52 | bool_comparison = false
53 | ```
54 |
55 | This example config will enable a `panic` checking lint (which is disabled by default), and disable a `bool_comparison` lint (which is enabled by default).
56 |
--------------------------------------------------------------------------------
/.github/workflows/website-deploy.yml:
--------------------------------------------------------------------------------
1 | name: Website Deploy
2 |
3 | on:
4 | workflow_call:
5 | inputs:
6 | target_branch:
7 | description: "Git branch to deploy from"
8 | required: true
9 | type: string
10 |
11 | workflow_dispatch:
12 | inputs:
13 | target_branch:
14 | description: "Git branch to deploy from"
15 | type: string
16 | default: "main"
17 |
18 | permissions:
19 | contents: read
20 | pages: write
21 | id-token: write
22 |
23 | concurrency:
24 | group: "website-deploy"
25 | cancel-in-progress: true
26 |
27 | jobs:
28 | validate-branch:
29 | name: Validate target branch
30 | runs-on: ubuntu-latest
31 | steps:
32 | - name: Set target ref
33 | run: |
34 | VERSION_REGEX="^v[0-9]+\.[0-9]+\.[0-9]+$"
35 | INPUT_BRANCH="${{ inputs.target_branch }}"
36 |
37 | # If the branch does not match the version regex, exit with failure.
38 | if [[ ! $INPUT_BRANCH =~ $VERSION_REGEX ]]; then
39 | exit 1
40 | fi
41 | build:
42 | name: Build website
43 | runs-on: ubuntu-latest
44 | needs:
45 | - validate-branch
46 | defaults:
47 | run:
48 | working-directory: ./website
49 | steps:
50 | - uses: actions/checkout@v6
51 | with:
52 | ref: ${{ inputs.target_ref }}
53 | - uses: dtolnay/rust-toolchain@master
54 | with:
55 | toolchain: nightly
56 | - uses: actions/setup-node@v6
57 | with:
58 | node-version: "22.x"
59 | - uses: actions/configure-pages@v5
60 |
61 | - name: Update docs
62 | working-directory: .
63 | run: cargo xtask update-docs
64 | - run: npm ci
65 | - run: npm run build
66 | - name: Upload GitHub Pages artifact
67 | uses: actions/upload-pages-artifact@v4
68 | with:
69 | path: website/.vitepress/dist
70 |
71 | deploy:
72 | environment:
73 | name: github-pages
74 | url: ${{ steps.deployment.outputs.page_url }}
75 | runs-on: ubuntu-latest
76 | needs:
77 | - validate-branch
78 | - build
79 | steps:
80 | - name: Deploy to GitHub Pages
81 | id: deployment
82 | uses: actions/deploy-pages@v4
83 |
--------------------------------------------------------------------------------
/website/.vitepress/components/Home.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 | A collection of lints to catch common mistakes and improve your Cairo code
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
Community
22 |
23 | Cairo lint is created by the same team at
24 | Software Mansion
25 | that is behind
26 | Scarb,
27 | the Cairo build toolchain as well as package manager.
28 |
29 | Feel free chat with us on our channel on
30 | Telegram.
31 |
32 |
33 |
34 |
98 |
--------------------------------------------------------------------------------
/src/lints/duplicate_underscore_args.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashSet;
2 |
3 | use crate::context::{CairoLintKind, Lint};
4 | use cairo_lang_defs::{ids::ModuleItemId, plugin::PluginDiagnostic};
5 | use cairo_lang_diagnostics::Severity;
6 | use cairo_lang_semantic::items::function_with_body::FunctionWithBodySemantic;
7 |
8 | use crate::queries::get_all_checkable_functions;
9 | use salsa::Database;
10 |
11 | pub struct DuplicateUnderscoreArgs;
12 |
13 | /// ## What it does
14 | ///
15 | /// Checks for functions that have the same argument name but prefix with `_`.
16 | ///
17 | /// ## Example
18 | ///
19 | /// This code will raise a warning because it can be difficult to differentiate between `test` and `_test`.
20 | ///
21 | /// ```cairo
22 | /// fn foo(test: u32, _test: u32) {}
23 | /// ```
24 | impl Lint for DuplicateUnderscoreArgs {
25 | fn allowed_name(&self) -> &'static str {
26 | "duplicate_underscore_args"
27 | }
28 |
29 | fn diagnostic_message(&self) -> &'static str {
30 | "duplicate arguments, having another argument having almost the same name \
31 | makes code comprehension and documentation more difficult"
32 | }
33 |
34 | fn kind(&self) -> CairoLintKind {
35 | CairoLintKind::DuplicateUnderscoreArgs
36 | }
37 | }
38 |
39 | #[tracing::instrument(skip_all, level = "trace")]
40 | pub fn check_duplicate_underscore_args<'db>(
41 | db: &'db dyn Database,
42 | item: &ModuleItemId<'db>,
43 | diagnostics: &mut Vec>,
44 | ) {
45 | let functions = get_all_checkable_functions(db, item);
46 |
47 | for function in functions {
48 | let mut registered_names: HashSet = HashSet::new();
49 | let params = db
50 | .function_with_body_signature(function)
51 | .cloned()
52 | .unwrap()
53 | .params;
54 |
55 | for param in params {
56 | let name_string = param.name.to_string(db);
57 | let stripped_name = name_string.strip_prefix('_').unwrap_or(&name_string);
58 |
59 | if !registered_names.insert(stripped_name.to_string()) {
60 | diagnostics.push(PluginDiagnostic {
61 | stable_ptr: param.stable_ptr.0,
62 | message: DuplicateUnderscoreArgs.diagnostic_message().to_string(),
63 | severity: Severity::Warning,
64 | inner_span: None,
65 | });
66 | }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/mappings.rs:
--------------------------------------------------------------------------------
1 | use cairo_lang_defs::ids::{LanguageElementId, ModuleItemId};
2 | use cairo_lang_filesystem::{db::get_originating_location, ids::SpanInFile};
3 | use cairo_lang_syntax::node::{SyntaxNode, ast::ModuleItem, ids::SyntaxStablePtrId};
4 | use cairo_language_common::CommonGroup;
5 | use salsa::Database;
6 |
7 | #[tracing::instrument(level = "trace", skip(db))]
8 | pub fn get_origin_module_item_as_syntax_node<'db>(
9 | db: &'db dyn Database,
10 | module_item_id: &ModuleItemId<'db>,
11 | ) -> Option> {
12 | let ptr = module_item_id.stable_location(db).stable_ptr();
13 | let SpanInFile { file_id, span } = get_originating_location(
14 | db,
15 | SpanInFile {
16 | file_id: ptr.file_id(db),
17 | span: ptr.lookup(db).span_without_trivia(db),
18 | },
19 | None,
20 | );
21 |
22 | db.find_syntax_node_at_offset(file_id, span.start)?
23 | .ancestors_with_self(db)
24 | .find(|n| ModuleItem::is_variant(n.kind(db)))
25 | }
26 |
27 | /// Returns the originating syntax node for a given stable pointer.
28 | #[tracing::instrument(level = "trace", skip(db))]
29 | pub fn get_origin_syntax_node<'db>(
30 | db: &'db dyn Database,
31 | ptr: &SyntaxStablePtrId<'db>,
32 | ) -> Option> {
33 | let syntax_node = ptr.lookup(db);
34 | let SpanInFile { file_id, span } = get_originating_location(
35 | db,
36 | SpanInFile {
37 | file_id: ptr.file_id(db),
38 | span: ptr.lookup(db).span_without_trivia(db),
39 | },
40 | None,
41 | );
42 |
43 | // Heuristically find the syntax node at the given offset.
44 | // We match the ancestors with node text to ensure we get the whole node.
45 | return db
46 | .find_syntax_node_at_offset(file_id, span.start)?
47 | .ancestors_with_self(db)
48 | .find(|node| node.get_text_without_trivia(db) == syntax_node.get_text_without_trivia(db));
49 | }
50 |
51 | #[tracing::instrument(level = "trace", skip(db))]
52 | pub fn get_originating_syntax_node_for<'db>(
53 | db: &'db dyn Database,
54 | ptr: &SyntaxStablePtrId<'db>,
55 | ) -> Option> {
56 | let SpanInFile { file_id, span } = get_originating_location(
57 | db,
58 | SpanInFile {
59 | file_id: ptr.file_id(db),
60 | span: ptr.lookup(db).span_without_trivia(db),
61 | },
62 | None,
63 | );
64 |
65 | db.find_syntax_node_at_offset(file_id, span.start)
66 | }
67 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | tags:
8 | - "v[0-9]+.[0-9]+.[0-9]+"
9 | pull_request:
10 | merge_group:
11 |
12 | permissions:
13 | contents: read
14 |
15 | jobs:
16 | checks:
17 | name: Checks
18 | runs-on: ubuntu-latest
19 | steps:
20 | - uses: actions/checkout@v6
21 | - uses: dtolnay/rust-toolchain@stable
22 | - uses: Swatinem/rust-cache@v2
23 | - run: cargo fmt --all --check
24 | - run: cargo lint
25 | env:
26 | RUSTFLAGS: "-Dwarnings"
27 | - run: cargo docs
28 | env:
29 | RUSTDOCFLAGS: "-Dwarnings"
30 | - uses: taiki-e/install-action@cargo-machete
31 | - run: cargo machete
32 | build-test:
33 | name: Build tests
34 | runs-on: ubuntu-latest
35 | steps:
36 | - uses: actions/checkout@v6
37 | - uses: dtolnay/rust-toolchain@stable
38 | - uses: Swatinem/rust-cache@v2
39 | - uses: taiki-e/install-action@nextest
40 | - name: Run nextest archive
41 | run: cargo nextest archive --workspace --all-features --cargo-profile ci --archive-file 'nextest-archive.tar.zst'
42 | - uses: actions/upload-artifact@v5
43 | with:
44 | name: nextest-archive
45 | path: nextest-archive.tar.zst
46 | test:
47 | name: Test ${{ matrix.partition}}/4
48 | runs-on: ubuntu-latest
49 | needs:
50 | - build-test
51 | strategy:
52 | fail-fast: false
53 | matrix:
54 | partition: [1, 2, 3, 4]
55 | steps:
56 | - uses: actions/checkout@v6
57 | - uses: dtolnay/rust-toolchain@stable
58 | - uses: software-mansion/setup-scarb@v1
59 | with:
60 | cache: false
61 | - uses: taiki-e/install-action@nextest
62 | - uses: actions/download-artifact@v6
63 | with:
64 | name: nextest-archive
65 | - name: Run nextest partition ${{ matrix.partition }}/4
66 | run: cargo nextest run --partition 'count:${{ matrix.partition }}/4' --archive-file 'nextest-archive.tar.zst'
67 | check-website:
68 | name: Website prettier check
69 | runs-on: ubuntu-latest
70 | defaults:
71 | run:
72 | working-directory: ./website
73 | steps:
74 | - uses: actions/checkout@v6
75 | - uses: actions/setup-node@v6
76 | with:
77 | node-version: "18.x"
78 | cache: npm
79 | cache-dependency-path: website/package-lock.json
80 | - run: npm ci
81 | - run: npm run fmt:check
82 |
--------------------------------------------------------------------------------
/tests/redundant_op/mod.rs:
--------------------------------------------------------------------------------
1 | use super::{test_lint_diagnostics, test_lint_fixer};
2 |
3 | const ADDITION_BY_ZERO: &str = r#"
4 | fn main() {
5 | let x = 42;
6 | let _y = x + 0;
7 | }
8 | "#;
9 | const SUBTRACTION_BY_ZERO: &str = r#"
10 | fn main() {
11 | let x = 42;
12 | let _y = x - 0;
13 | }
14 | "#;
15 | const MULTIPLICATION_BY_ONE: &str = r#"
16 | fn main() {
17 | let x = 42;
18 | let _y = x * 1;
19 | }
20 | "#;
21 | const DIVISION_BY_ONE: &str = r#"
22 | fn main() {
23 | let x = 42_u32;
24 | let _y = x / 1;
25 | }
26 | "#;
27 |
28 | #[test]
29 | fn addition_by_zero_diagnostics() {
30 | test_lint_diagnostics!(ADDITION_BY_ZERO, @r"
31 | Plugin diagnostic: This operation doesn't change the value and can be simplified.
32 | --> lib.cairo:4:14
33 | let _y = x + 0;
34 | ^^^^^
35 | ");
36 | }
37 | #[test]
38 | fn addition_by_zero_fixer() {
39 | test_lint_fixer!(ADDITION_BY_ZERO, @r"
40 | fn main() {
41 | let x = 42;
42 | let _y = x + 0;
43 | }
44 | ");
45 | }
46 | #[test]
47 | fn subtraction_by_zero_diagnostics() {
48 | test_lint_diagnostics!(SUBTRACTION_BY_ZERO, @r"
49 | Plugin diagnostic: This operation doesn't change the value and can be simplified.
50 | --> lib.cairo:4:14
51 | let _y = x - 0;
52 | ^^^^^
53 | ");
54 | }
55 | #[test]
56 | fn subtraction_by_zero_fixer() {
57 | test_lint_fixer!(SUBTRACTION_BY_ZERO, @r"
58 | fn main() {
59 | let x = 42;
60 | let _y = x - 0;
61 | }
62 | ");
63 | }
64 | #[test]
65 | fn multiplication_by_one_diagnostics() {
66 | test_lint_diagnostics!(MULTIPLICATION_BY_ONE, @r"
67 | Plugin diagnostic: This operation doesn't change the value and can be simplified.
68 | --> lib.cairo:4:14
69 | let _y = x * 1;
70 | ^^^^^
71 | ");
72 | }
73 | #[test]
74 | fn multiplication_by_one_fixer() {
75 | test_lint_fixer!(MULTIPLICATION_BY_ONE, @r#"
76 | fn main() {
77 | let x = 42;
78 | let _y = x * 1;
79 | }
80 | "#);
81 | }
82 | #[test]
83 | fn division_by_one_diagnostics() {
84 | test_lint_diagnostics!(DIVISION_BY_ONE, @r"
85 | Plugin diagnostic: This operation doesn't change the value and can be simplified.
86 | --> lib.cairo:4:14
87 | let _y = x / 1;
88 | ^^^^^
89 | ");
90 | }
91 | #[test]
92 | fn division_by_one_fixer() {
93 | test_lint_fixer!(DIVISION_BY_ONE, @r#"
94 | fn main() {
95 | let x = 42_u32;
96 | let _y = x / 1;
97 | }
98 | "#);
99 | }
100 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "cairo-lint"
3 | description = "Core library for linting Cairo language code."
4 | version.workspace = true
5 | edition.workspace = true
6 | repository.workspace = true
7 | license-file.workspace = true
8 |
9 |
10 | [workspace]
11 | resolver = "2"
12 | members = ["xtask"]
13 |
14 | [workspace.package]
15 | version = "2.15.0-rc.1"
16 | edition = "2024"
17 | repository = "https://github.com/software-mansion/cairo-lint"
18 | license-file = "LICENSE"
19 |
20 | # Managing dependencies on crates from starkware-libs/cairo repository:
21 | #
22 | # The Cairo compiler is made of a bunch of crates that inter-depend on each other and have
23 | # synchronised versioning.
24 | # It is very important to use a single revision of these crates in the entire Cairo toolchain,
25 | # which consists of Cairo compiler, Scarb, CairoLS and other tools.
26 | # The toolchain is eventually built by Scarb, which depends on everything other as regular crates.
27 | # To ensure that all crates in the toolchain use the same revision of Cairo crates, we use a patch
28 | # mechanism that Cargo provides.
29 | # Because Cargo requires patches to change the crate source, we have an unspoken contract that
30 | # all tools *always* depend on some crates.io versions of Cairo crates and Scarb uses
31 | # [patch.crates.io] table to set final git revision for everything.
32 | #
33 | # To keep our Cargo.toml following this contract, always use `cargo xtask upgrade`
34 | # for manipulating these dependencies.
35 | [dependencies]
36 | anyhow = "1.0.100"
37 | cairo-lang-compiler = "2.15.0-rc.1"
38 | cairo-lang-defs = "2.15.0-rc.1"
39 | cairo-lang-diagnostics = "2.15.0-rc.1"
40 | cairo-lang-filesystem = "2.15.0-rc.1"
41 | cairo-lang-formatter = "2.15.0-rc.1"
42 | cairo-lang-parser = "2.15.0-rc.1"
43 | cairo-lang-semantic = "2.15.0-rc.1"
44 | cairo-lang-syntax = "2.15.0-rc.1"
45 | cairo-lang-test-plugin = "2.15.0-rc.1"
46 | cairo-lang-utils = "2.15.0-rc.1"
47 | cairo-language-common = "2.15.0-rc.1"
48 | if_chain = "1.0.3"
49 | indoc = "2"
50 | itertools = "0.14.0"
51 | log = "0.4.28"
52 | num-bigint = "0.4.6"
53 | salsa = "0.24.0"
54 | scarb-metadata = "1.15.1"
55 | tempfile = "3"
56 | tracing = "0.1"
57 | which = "8"
58 |
59 | [dev-dependencies]
60 | ctor = "0.6.1"
61 | insta = "1.44.3"
62 | pretty_assertions = "1.4.1"
63 | test-case = "3.0"
64 |
65 | # Here we specify real dependency specifications for Cairo crates *if* currently we want to use
66 | # a particular unreleased commit (which is frequent mid-development).
67 | # We list all Cairo crates that go into Scarb's compilation unit even if Scarb itself does not depend
68 | # on some of them directly.
69 | # This ensures no duplicate instances of Cairo crates are pulled in by mistake.
70 | [patch.crates-io]
71 |
72 | [profile.ci]
73 | inherits = "test"
74 | strip = "debuginfo"
75 | [profile.ci.package."salsa"]
76 | opt-level = 3
77 |
78 |
--------------------------------------------------------------------------------
/src/fixer/db.rs:
--------------------------------------------------------------------------------
1 | use cairo_lang_defs::db::{defs_group_input, init_external_files};
2 | use cairo_lang_filesystem::db::files_group_input;
3 | use cairo_lang_semantic::db::semantic_group_input;
4 |
5 | use salsa::{Database, Setter};
6 |
7 | #[salsa::db]
8 | #[derive(Clone)]
9 | pub struct FixerDatabase {
10 | storage: salsa::Storage,
11 | }
12 |
13 | impl salsa::Database for FixerDatabase {}
14 |
15 | impl FixerDatabase {
16 | pub fn new_from(db: &dyn Database) -> Self {
17 | let mut new_db = Self::new();
18 | // SemanticGroup salsa inputs.
19 | semantic_group_input(&new_db)
20 | .set_default_analyzer_plugins(&mut new_db)
21 | .to(semantic_group_input(db)
22 | .default_analyzer_plugins(db)
23 | .clone());
24 | semantic_group_input(&new_db)
25 | .set_analyzer_plugin_overrides(&mut new_db)
26 | .to(semantic_group_input(db)
27 | .analyzer_plugin_overrides(db)
28 | .clone());
29 |
30 | // DefsGroup salsa inputs.
31 | defs_group_input(&new_db)
32 | .set_default_macro_plugins(&mut new_db)
33 | .to(defs_group_input(db).default_macro_plugins(db).clone());
34 | defs_group_input(&new_db)
35 | .set_macro_plugin_overrides(&mut new_db)
36 | .to(defs_group_input(db).macro_plugin_overrides(db).clone());
37 | defs_group_input(&new_db)
38 | .set_default_inline_macro_plugins(&mut new_db)
39 | .to(defs_group_input(db)
40 | .default_inline_macro_plugins(db)
41 | .clone());
42 | defs_group_input(&new_db)
43 | .set_inline_macro_plugin_overrides(&mut new_db)
44 | .to(defs_group_input(db)
45 | .inline_macro_plugin_overrides(db)
46 | .clone());
47 |
48 | // FilesGroup salsa inputs.
49 | files_group_input(&new_db)
50 | .set_crate_configs(&mut new_db)
51 | .to(files_group_input(db).crate_configs(db).clone());
52 | files_group_input(&new_db)
53 | .set_file_overrides(&mut new_db)
54 | .to(files_group_input(db).file_overrides(db).clone());
55 | files_group_input(&new_db)
56 | .set_flags(&mut new_db)
57 | .to(files_group_input(db).flags(db).clone());
58 | files_group_input(&new_db)
59 | .set_cfg_set(&mut new_db)
60 | .to(files_group_input(db).cfg_set(db).clone());
61 |
62 | // Initiate it again instead of migrating because [`ExternalFiles.try_ext_as_virtual_obj`] is private.
63 | // We can do that since the only thing in this input is an `Arc` to a closure,
64 | // that is never supposed to be changed after the initialization.
65 | init_external_files(&mut new_db);
66 |
67 | new_db
68 | }
69 |
70 | fn new() -> Self {
71 | Self {
72 | storage: Default::default(),
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/lints/redundant_op.rs:
--------------------------------------------------------------------------------
1 | use super::{ADD, DIV, MUL, SUB};
2 | use crate::context::{CairoLintKind, Lint};
3 |
4 | use crate::helper::{is_one, is_zero};
5 | use crate::lints::function_trait_name_from_fn_id;
6 | use crate::queries::{get_all_function_bodies, get_all_function_calls};
7 | use cairo_lang_defs::ids::ModuleItemId;
8 | use cairo_lang_defs::plugin::PluginDiagnostic;
9 | use cairo_lang_diagnostics::Severity;
10 | use cairo_lang_semantic::{Arenas, ExprFunctionCall};
11 | use cairo_lang_syntax::node::TypedStablePtr;
12 | use salsa::Database;
13 |
14 | pub struct RedundantOperation;
15 |
16 | /// ## What it does
17 | ///
18 | /// Checks for redundant arithmetic operations like `x + 0`, `x - 0`, `x * 1`, `x / 1`
19 | ///
20 | /// ## Example
21 | ///
22 | /// ```cairo
23 | /// fn main() {
24 | /// let x = 42;
25 | /// let _y = x * 1;
26 | /// }
27 | /// ```
28 | ///
29 | /// Can be simplified to
30 | ///
31 | /// ```cairo
32 | /// fn main() {
33 | /// let x = 42;
34 | /// let _y = x;
35 | /// }
36 | /// ```
37 | impl Lint for RedundantOperation {
38 | fn allowed_name(&self) -> &'static str {
39 | "redundant_op"
40 | }
41 |
42 | fn diagnostic_message(&self) -> &'static str {
43 | "This operation doesn't change the value and can be simplified."
44 | }
45 |
46 | fn kind(&self) -> CairoLintKind {
47 | CairoLintKind::RedundantOperation
48 | }
49 | }
50 |
51 | #[tracing::instrument(skip_all, level = "trace")]
52 | pub fn check_redundant_operation<'db>(
53 | db: &'db dyn Database,
54 | item: &ModuleItemId<'db>,
55 | diagnostics: &mut Vec>,
56 | ) {
57 | let function_bodies = get_all_function_bodies(db, item);
58 | for function_body in function_bodies.iter() {
59 | let function_call_exprs = get_all_function_calls(function_body);
60 | let arenas = &function_body.arenas;
61 | for function_call_expr in function_call_exprs {
62 | check_single_redundant_operation(db, &function_call_expr, arenas, diagnostics);
63 | }
64 | }
65 | }
66 |
67 | fn check_single_redundant_operation<'db>(
68 | db: &'db dyn Database,
69 | expr_func: &ExprFunctionCall<'db>,
70 | arenas: &Arenas<'db>,
71 | diagnostics: &mut Vec>,
72 | ) {
73 | let func = function_trait_name_from_fn_id(db, &expr_func.function);
74 | let is_redundant = match func.as_str() {
75 | ADD => is_zero(&expr_func.args[0], arenas) || is_zero(&expr_func.args[1], arenas),
76 | SUB => is_zero(&expr_func.args[1], arenas),
77 | MUL => is_one(&expr_func.args[0], arenas) || is_one(&expr_func.args[1], arenas),
78 | DIV => is_one(&expr_func.args[1], arenas),
79 | _ => false,
80 | };
81 |
82 | if is_redundant {
83 | diagnostics.push(PluginDiagnostic {
84 | stable_ptr: expr_func.stable_ptr.untyped(),
85 | message: RedundantOperation.diagnostic_message().to_string(),
86 | severity: Severity::Warning,
87 | inner_span: None,
88 | });
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/lints/erasing_op.rs:
--------------------------------------------------------------------------------
1 | use cairo_lang_defs::ids::ModuleItemId;
2 | use cairo_lang_defs::plugin::PluginDiagnostic;
3 | use cairo_lang_diagnostics::Severity;
4 | use cairo_lang_semantic::{Arenas, ExprFunctionCall};
5 | use cairo_lang_syntax::node::TypedStablePtr;
6 |
7 | use super::{AND, function_trait_name_from_fn_id};
8 | use crate::context::{CairoLintKind, Lint};
9 |
10 | use crate::helper::is_zero;
11 | use crate::lints::{DIV, MUL};
12 | use crate::queries::{get_all_function_bodies, get_all_function_calls};
13 | use salsa::Database;
14 |
15 | pub struct ErasingOperation;
16 |
17 | /// ## What it does
18 | ///
19 | /// Checks for operations that result in the value being erased (e.g., multiplication by 0 or 0 being divided by anything).
20 | ///
21 | /// ## Example
22 | ///
23 | /// ```cairo
24 | /// fn main() {
25 | /// let x = 1;
26 | /// let _y = 0 * x;
27 | /// let _z = 0 / x;
28 | /// let _c = x & 0;
29 | /// }
30 | /// ```
31 | ///
32 | /// Could be simplified by replacing the entire expression with 0:
33 | ///
34 | /// ```cairo
35 | /// fn main() {
36 | /// let x = 1;
37 | /// let _y = 0;
38 | /// let _z = 0;
39 | /// let _c = 0;
40 | /// }
41 | /// ```
42 | impl Lint for ErasingOperation {
43 | fn allowed_name(&self) -> &'static str {
44 | "erasing_op"
45 | }
46 |
47 | fn diagnostic_message(&self) -> &'static str {
48 | "This operation results in the value being erased (e.g., multiplication by 0). \
49 | Consider replacing the entire expression with 0."
50 | }
51 |
52 | fn kind(&self) -> CairoLintKind {
53 | CairoLintKind::ErasingOperation
54 | }
55 | }
56 |
57 | #[tracing::instrument(skip_all, level = "trace")]
58 | pub fn check_erasing_operation<'db>(
59 | db: &'db dyn Database,
60 | item: &ModuleItemId<'db>,
61 | diagnostics: &mut Vec>,
62 | ) {
63 | let function_bodies = get_all_function_bodies(db, item);
64 | for function_body in function_bodies.iter() {
65 | let function_call_exprs = get_all_function_calls(function_body);
66 | let arenas = &function_body.arenas;
67 | for function_call_expr in function_call_exprs {
68 | check_single_erasing_operation(db, &function_call_expr, arenas, diagnostics);
69 | }
70 | }
71 | }
72 |
73 | fn check_single_erasing_operation<'db>(
74 | db: &'db dyn Database,
75 | expr_func: &ExprFunctionCall<'db>,
76 | arenas: &Arenas<'db>,
77 | diagnostics: &mut Vec>,
78 | ) {
79 | let func = function_trait_name_from_fn_id(db, &expr_func.function);
80 |
81 | let is_erasing_operation = match func.as_str() {
82 | MUL | AND => is_zero(&expr_func.args[0], arenas) || is_zero(&expr_func.args[1], arenas),
83 | DIV => is_zero(&expr_func.args[0], arenas),
84 | _ => false,
85 | };
86 | if is_erasing_operation {
87 | diagnostics.push(PluginDiagnostic {
88 | stable_ptr: expr_func.stable_ptr.untyped(),
89 | message: ErasingOperation.diagnostic_message().to_string(),
90 | severity: Severity::Warning,
91 | inner_span: None,
92 | });
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution Guideline
2 |
3 |
4 | ## Environment setup
5 |
6 | - Install the latest Rust via [rustup](https://doc.rust-lang.org/cargo/getting-started/installation.html).
7 | - Install the latest [Scarb via ASDF](https://docs.swmansion.com/scarb/download.html#install-via-asdf).
8 |
9 | ## Contributing
10 |
11 | - Before you open a pull request, it is always a good idea to search the issues and verify if the feature you would like
12 | to add hasn't been already discussed.
13 | - We also appreciate creating a feature request before making a contribution, so it can be discussed before you get to
14 | work.
15 | - If the change you are introducing is changing or breaking the behavior of any already existing features, make sure to
16 | include that information in the pull request description.
17 |
18 | ## Adding new lint rule
19 |
20 | In order to add a new rule, you must extend a [context](src/context.rs) with a new lint or whole lint group.
21 |
22 | Each individual lint rule should be documented. When implementing [Lint trait](src/context.rs#L118) for the Lint rule, remember to include a documentation for it which should look like this:
23 |
24 | ```rust
25 | /// ## What it does
26 | ///
27 | /// ## Example
28 | ///
29 | /// ```cairo
30 | /// // example code
31 | /// ```
32 | impl Lint for MyRule {
33 | // implementation ...
34 | }
35 | ```
36 |
37 | ## Updating documentation
38 |
39 | The documentation lives inside the `website` directory. The content is mainly autogenerated by
40 | ```bash
41 | cargo xtask update-docs
42 | ```
43 | After implementing a new lint or after modifying old one's documentation, it is mandatory to update the documentation website by running the script above.
44 |
45 | ## Testing
46 |
47 | ### Running tests
48 |
49 | To run the tests, just use:
50 |
51 | ```sh
52 | cargo test
53 | ```
54 |
55 | Remember to have `scarb` in your PATH, as it's used to resolve corelib used for testing. The Scarb version that should be used if testing is specified in [.tool-versions](.tool-versions) file.
56 |
57 | If you don't have an access to scarb binary, or you want to use specific version of the corelib during testing, just run:
58 |
59 | ```sh
60 | CORELIB_PATH="/path/to/corelib/src" cargo test
61 | ```
62 |
63 | and use any corelib version you want.
64 |
65 | ### Reviewing snapshot changes
66 |
67 | ```sh
68 | cargo insta review
69 | ```
70 |
71 | ### Manual instructions
72 |
73 | Each lint should have its own tests and should be extensive. To create a new test for a lint you need to create a new file/module
74 | in the [test_files folder](tests) and should be named as your lint. The file should
75 |
76 | As for tests, we are using [insta](https://insta.rs/) snapshot library.
77 | There are 2 testing macros:
78 | - [test_lint_diagnostics](tests/helpers/mod.rs)
79 | - [test_lint_fixer](tests/helpers/mod.rs)
80 |
81 | Tests should use only the inline snapshots.
82 |
83 |
84 | When creating a new test, you can run `CORELIB_PATH={path} cargo test`, and see if your snapshots match. It's recommended to use the the [cargo-insta](https://crates.io/crates/cargo-insta) tool to review the snapshots. Just remember to first run the tests with `cargo test`, and after that run `cargo insta review` to review any snapshot differences.
85 |
--------------------------------------------------------------------------------
/src/lints/mod.rs:
--------------------------------------------------------------------------------
1 | use cairo_lang_defs::ids::{FunctionWithBodyId, TopLevelLanguageElementId};
2 | use cairo_lang_semantic::FunctionId;
3 | use cairo_lang_semantic::items::imp::ImplSemantic;
4 | use salsa::Database;
5 |
6 | pub mod bitwise_for_parity_check;
7 | pub mod bool_comparison;
8 | pub mod breaks;
9 | pub mod clone_on_copy;
10 | pub mod collapsible_match;
11 | pub mod double_comparison;
12 | pub mod double_parens;
13 | pub mod duplicate_underscore_args;
14 | pub mod empty_enum_brackets_variant;
15 | pub mod enum_variant_names;
16 | pub mod eq_op;
17 | pub mod erasing_op;
18 | pub mod ifs;
19 | pub mod int_op_one;
20 | pub mod loops;
21 | pub mod manual;
22 | pub mod panic;
23 | pub mod performance;
24 | pub mod redundant_brackets_in_enum_call;
25 | pub mod redundant_into;
26 | pub mod redundant_op;
27 | pub mod single_match;
28 | pub mod unit_return_type;
29 | pub mod unwrap_syscall;
30 |
31 | pub(crate) const LE: &str = "core::traits::PartialOrd::le";
32 | pub(crate) const GE: &str = "core::traits::PartialOrd::ge";
33 | pub(crate) const LT: &str = "core::traits::PartialOrd::lt";
34 | pub(crate) const GT: &str = "core::traits::PartialOrd::gt";
35 | pub(crate) const EQ: &str = "core::traits::PartialEq::eq";
36 | pub(crate) const NE: &str = "core::traits::PartialEq::ne";
37 | pub(crate) const AND: &str = "core::traits::BitAnd::bitand";
38 | pub(crate) const OR: &str = "core::traits::BitOr::bitor";
39 | pub(crate) const XOR: &str = "core::traits::BitXor::bitxor";
40 | pub(crate) const NOT: &str = "core::traits::BitNot::bitnot";
41 | pub(crate) const DIV: &str = "core::traits::Div::div";
42 | pub(crate) const MUL: &str = "core::traits::Mul::mul";
43 | pub(crate) const ADD: &str = "core::traits::Add::add";
44 | pub(crate) const SUB: &str = "core::traits::Sub::sub";
45 | pub(crate) const OK: &str = "core::result::Result::Ok";
46 | pub(crate) const ERR: &str = "core::result::Result::Err";
47 | pub(crate) const SOME: &str = "core::option::Option::Some";
48 | pub(crate) const NONE: &str = "core::option::Option::None";
49 | pub(crate) const TRUE: &str = "core::bool::True";
50 | pub(crate) const FALSE: &str = "core::bool::False";
51 | pub(crate) const PANIC_WITH_FELT252: &str = "core::panic_with_felt252";
52 | pub(crate) const DEFAULT: &str = "core::traits::Default::default";
53 | pub(crate) const ARRAY_NEW: &str = "core::array::ArrayTrait::new";
54 | pub(crate) const NEVER: &str = "core::never";
55 | pub(crate) const SPAN: &str = "core::array::Span";
56 | pub(crate) const ARRAY: &str = "core::array::Array";
57 | pub(crate) const U32: &str = "core::integer::u32";
58 |
59 | pub(crate) fn function_trait_name_from_fn_id<'db>(
60 | db: &'db dyn Database,
61 | function: &FunctionId<'db>,
62 | ) -> String {
63 | let Ok(Some(func_id)) = function.get_concrete(db).body(db) else {
64 | return String::new();
65 | };
66 | // Get the trait function id of the function (if there's none it means it cannot be a call to
67 | // a corelib trait)
68 | let trait_fn_id = match func_id.function_with_body_id(db) {
69 | FunctionWithBodyId::Impl(func) => db.impl_function_trait_function(func).unwrap(),
70 | FunctionWithBodyId::Trait(func) => func,
71 | _ => return String::new(),
72 | };
73 | // From the trait function id get the trait name and check if it's the corelib `BitAnd`
74 | trait_fn_id.full_path(db)
75 | }
76 |
--------------------------------------------------------------------------------
/tests/helpers/scarb.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | fs,
3 | path::{Path, PathBuf},
4 | sync::OnceLock,
5 | };
6 |
7 | use anyhow::{Context, Result};
8 | use cairo_lang_filesystem::db::CORELIB_CRATE_NAME;
9 | use indoc::indoc;
10 | use scarb_metadata::{Metadata, MetadataCommand};
11 | use tempfile::tempdir;
12 | use which::which;
13 |
14 | pub const SCARB_TOML: &str = "Scarb.toml";
15 |
16 | fn get_scarb_path() -> Result {
17 | which("scarb").map_err(|_| anyhow::anyhow!("`scarb` not found in `PATH`"))
18 | }
19 |
20 | /// Calls `scarb metadata` on an empty Scarb package to find the `core` package.
21 | fn get_scarb_metadata(manifest: &Path) -> Result {
22 | let scarb_path = get_scarb_path()?;
23 |
24 | MetadataCommand::new()
25 | .scarb_path(scarb_path)
26 | .manifest_path(manifest)
27 | .inherit_stderr()
28 | .exec()
29 | .context("failed to execute: scarb metadata")
30 | }
31 |
32 | /// Try to find a Scarb-managed `core` package if we have Scarb toolchain.
33 | ///
34 | /// The easiest way to do this is to create an empty Scarb package and run `scarb metadata` on it.
35 | /// The `core` package will be a component of this empty package.
36 | /// For minimal packages, `scarb metadata` should be pretty fast.
37 | pub fn find_scarb_managed_core() -> Option {
38 | let lookup = || {
39 | let workspace = tempdir()
40 | .context("failed to create temporary directory")
41 | .inspect_err(|e| eprintln!("{e:?}"))
42 | .ok()?;
43 |
44 | let scarb_toml = workspace.path().join(SCARB_TOML);
45 | fs::write(
46 | &scarb_toml,
47 | indoc! {r#"
48 | [package]
49 | name = "cairo_lint_unmanaged_core_lookup"
50 | version = "1.0.0"
51 | "#},
52 | )
53 | .context("failed to write Scarb.toml")
54 | .inspect_err(|e| eprintln!("{e:?}"))
55 | .ok()?;
56 |
57 | let metadata = get_scarb_metadata(&scarb_toml)
58 | .inspect_err(|e| eprintln!("{e:?}"))
59 | .ok()?;
60 |
61 | // Ensure the workspace directory is deleted after running Scarb.
62 | // We are ignoring the error, leaving doing proper clean-up to the OS.
63 | let _ = workspace
64 | .close()
65 | .context("failed to wipe temporary directory")
66 | .inspect_err(|e| eprintln!("{e:?}"));
67 |
68 | // Scarb is expected to generate only one compilation unit (for our stub package)
69 | // that will consist of this package and the `core` crate.
70 | // Therefore, we allow ourselves to liberally just look for any first usage of a package
71 | // named `core` in all compilation units components we got.
72 | let path = metadata
73 | .compilation_units
74 | .into_iter()
75 | .find_map(|compilation_unit| {
76 | compilation_unit
77 | .components
78 | .iter()
79 | .find(|component| component.name == CORELIB_CRATE_NAME)
80 | .map(|component| component.source_root().to_path_buf().into_std_path_buf())
81 | })?;
82 |
83 | Some(path)
84 | };
85 |
86 | static CACHE: OnceLock