Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lint against approximated constants #574

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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",
);
}
}
20 changes: 20 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,20 @@
local good = 3
local good = 3.1
local good = 3.13
local good = 3.15
local good = -3.15
local good = 3.1417
local good = 3.14159266

local good = 0x314

local bad = 3.14
local bad = 3.141
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)
local bad = f(-3.14 + 1)
54 changes: 54 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,54 @@
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:14
13 │ local bad = -3.141
│ ^^^^^

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

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

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

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

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

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

Loading