Skip to content

Commit

Permalink
Lint against approximated constants
Browse files Browse the repository at this point in the history
  • Loading branch information
chriscerie committed Dec 2, 2023
1 parent 651ac70 commit 02c9d65
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased](https://github.com/Kampfkarren/selene/compare/0.26.1...HEAD)
### Added
- Added new [`approx_constant`](https://kampfkarren.github.io/selene/lints/approx_constant.html) lint, which will check for number literals that approximate constants.

## [0.26.1](https://github.com/Kampfkarren/selene/releases/tag/0.26.1) - 2023-11-11
### Fixed
Expand Down
1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- [Contributing](./contributing.md)
- [Lints](./lints/index.md)
- [almost_swapped](./lints/almost_swapped.md)
- [approx_constant](./lints/approx_constant.md)
- [constant_table_comparison](./lints/constant_table_comparison.md)
- [deprecated](./lints/deprecated.md)
- [divide_by_zero](./lints/divide_by_zero.md)
Expand Down
17 changes: 17 additions & 0 deletions docs/src/lints/approx_constant.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# approx_constant
## What it does
Checks for number literals that approximate constants.

## Why this is bad
Using constants provided by the Lua standard library is more precise.

## Example
```lua
local x = 3.14
```

...should be written as...

```lua
local x = math.pi
```
1 change: 1 addition & 0 deletions selene-lib/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use full_moon::{ast::Ast, node::Node};
use serde::de::DeserializeOwned;

pub mod almost_swapped;
pub mod approx_constant;
pub mod bad_string_escape;
pub mod compare_nan;
pub mod constant_table_comparison;
Expand Down
87 changes: 87 additions & 0 deletions selene-lib/src/lints/approx_constant.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use super::*;
use std::convert::Infallible;

use full_moon::{ast::Ast, tokenizer::TokenType, visitors::Visitor};

pub struct ApproxConstantLint;

impl Lint for ApproxConstantLint {
type Config = ();
type Error = Infallible;

const SEVERITY: Severity = Severity::Warning;
const LINT_TYPE: LintType = LintType::Correctness;

fn new((): Self::Config) -> Result<Self, Self::Error> {
Ok(ApproxConstantLint)
}

fn pass(&self, ast: &Ast, _: &Context, _: &AstContext) -> Vec<Diagnostic> {
let mut visitor = ApproxConstantVisitor {
approx_constants: Vec::new(),
};

visitor.visit_ast(ast);

visitor
.approx_constants
.iter()
.map(|constant| {
Diagnostic::new(
"approx_constant",
format!("`{}` is more precise", constant.constant),
Label::new(constant.range),
)
})
.collect()
}
}

struct ApproxConstantVisitor {
approx_constants: Vec<ApproximatedConstant>,
}

struct ApproximatedConstant {
range: (usize, usize),
constant: String,
}

impl Visitor for ApproxConstantVisitor {
fn visit_number(&mut self, token: &full_moon::tokenizer::Token) {
if let TokenType::Number { text } = token.token_type() {
if is_approx_const(std::f64::consts::PI, text, 3) {
self.approx_constants.push(ApproximatedConstant {
range: (token.start_position().bytes(), token.end_position().bytes()),
constant: "math.pi".to_string(),
});
}
}
}
}

#[must_use]
fn is_approx_const(constant: f64, value: &str, min_digits: usize) -> bool {
if value.len() <= min_digits {
false
} else if constant.to_string().starts_with(value) {
// The value is a truncated constant
true
} else {
let round_const = format!("{constant:.*}", value.len() - 2);
value == round_const
}
}

#[cfg(test)]
mod tests {
use super::{super::test_util::test_lint, *};

#[test]
fn test_approx_constant() {
test_lint(
ApproxConstantLint::new(()).unwrap(),
"approx_constant",
"approx_constant",
);
}
}
18 changes: 18 additions & 0 deletions selene-lib/tests/lints/approx_constant/approx_constant.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
local good = 3
local good = 3.1
local good = 3.13
local good = 3.15
local good = 3.1417
local good = 3.14159266

local good = 0x314
local good = 3_14

local bad = 3.14
local bad = 3.141
local bad = 3.142
local bad = 3.1415
local bad = 3.14159265

local bad = 3.14 + 1
local bad = f(3.14 + 1)
42 changes: 42 additions & 0 deletions selene-lib/tests/lints/approx_constant/approx_constant.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
error[approx_constant]: `math.pi` is more precise
┌─ approx_constant.lua:11:13
11 │ local bad = 3.14
│ ^^^^

error[approx_constant]: `math.pi` is more precise
┌─ approx_constant.lua:12:13
12 │ local bad = 3.141
│ ^^^^^

error[approx_constant]: `math.pi` is more precise
┌─ approx_constant.lua:13:13
13 │ local bad = 3.142
│ ^^^^^

error[approx_constant]: `math.pi` is more precise
┌─ approx_constant.lua:14:13
14 │ local bad = 3.1415
│ ^^^^^^

error[approx_constant]: `math.pi` is more precise
┌─ approx_constant.lua:15:13
15 │ local bad = 3.14159265
│ ^^^^^^^^^^

error[approx_constant]: `math.pi` is more precise
┌─ approx_constant.lua:17:13
17 │ local bad = 3.14 + 1
│ ^^^^

error[approx_constant]: `math.pi` is more precise
┌─ approx_constant.lua:18:15
18 │ local bad = f(3.14 + 1)
│ ^^^^

0 comments on commit 02c9d65

Please sign in to comment.