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

Add infrastructure for linting and creating new Lints #1140

Merged
merged 101 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from 88 commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
181b5e2
linter barebones
orpuente-MS Feb 4, 2024
737068c
basic demo
orpuente-MS Feb 5, 2024
477e38c
colored output
orpuente-MS Feb 5, 2024
5480cd3
added division by zero lint
orpuente-MS Feb 5, 2024
9ae026a
make LINT_BUFFER static and add lint declaration macros
orpuente-MS Feb 5, 2024
484eb80
add linter public api
orpuente-MS Feb 7, 2024
04eef46
add docs for the usage of the newtype idiom & remove unnecessary life…
orpuente-MS Feb 7, 2024
632b4ea
fix typo in docstring
orpuente-MS Feb 7, 2024
f793e90
reduce visibility
orpuente-MS Feb 7, 2024
fe8830b
combined lint pass for performance
orpuente-MS Feb 8, 2024
bd0365a
moved some things around
orpuente-MS Feb 8, 2024
189e20f
fix typos
orpuente-MS Feb 8, 2024
20ba97c
refactored tests
orpuente-MS Feb 8, 2024
0d7379c
improved docs
orpuente-MS Feb 8, 2024
fe28721
reduce visibility of drain()
orpuente-MS Feb 8, 2024
3f87d79
moved things arond
orpuente-MS Feb 8, 2024
1cad2f3
remove linter ui demo
orpuente-MS Feb 9, 2024
e9c7296
removed mut static buffer
orpuente-MS Feb 9, 2024
9063cc2
Merge branch 'main' of https://github.com/orpuente-MS/qsharp
orpuente-MS Feb 9, 2024
5dd58b6
integrated linter into language_server
orpuente-MS Feb 20, 2024
022c392
changed lint levels names
orpuente-MS Feb 20, 2024
3b449da
disable division by zero lint
orpuente-MS Feb 21, 2024
15f2483
filter out LintLevel::Allow lints
orpuente-MS Feb 21, 2024
d7fa0ba
Merge branch 'microsoft:main' into main
orpuente-MS Feb 21, 2024
e9cdcf6
Update compiler/qsc_linter/Cargo.toml
orpuente-MS Feb 27, 2024
b9c7db9
Add docstrings to LintPass traits
orpuente-MS Feb 27, 2024
7890971
Removed allow atributes across the crate
orpuente-MS Feb 27, 2024
a11d842
Removed empty hir lints file. Can be added again later.
orpuente-MS Feb 27, 2024
99f6d40
Merge branch 'main' of https://github.com/orpuente-MS/qsharp
orpuente-MS Feb 27, 2024
fd2eacc
removed #warn
orpuente-MS Feb 27, 2024
8825bdf
add #derive[(Default)] to CombinedLints
orpuente-MS Feb 27, 2024
ab613f0
Added better explanation for "speed" in the docstring for the Combine…
orpuente-MS Feb 27, 2024
2744fbe
added back warn and allow
orpuente-MS Feb 27, 2024
eed48db
Merge branch 'microsoft:main' into main
orpuente-MS Feb 27, 2024
af0958c
add default lang_features
orpuente-MS Feb 27, 2024
989a508
clean Cargo.toml
orpuente-MS Feb 28, 2024
781dd43
add unit tests
orpuente-MS Feb 28, 2024
852fb82
reverse changes in qsc_frontend
orpuente-MS Feb 28, 2024
7465ea9
remove unnecesary wrapper
orpuente-MS Feb 29, 2024
d2e6af2
delay computation of lints until necesary
orpuente-MS Feb 29, 2024
02734aa
remove unnecesary trait impl
orpuente-MS Feb 29, 2024
c4a47d9
prefix underscore unused fn params in default empty impl
orpuente-MS Feb 29, 2024
a941168
prefix underscore unused fn params in default empty impl
orpuente-MS Feb 29, 2024
ef3eee0
remove fir dependency
orpuente-MS Feb 29, 2024
0ce6413
added division by zero unit test
orpuente-MS Feb 29, 2024
33d0e07
small stylistic change
orpuente-MS Feb 29, 2024
c7feaeb
remove unnecesary nesting
orpuente-MS Mar 1, 2024
24457b3
fixed typo
orpuente-MS Mar 4, 2024
74744a8
added MIT License to new src files
orpuente-MS Mar 5, 2024
7c8e90d
Merge branch 'microsoft:main' into main
orpuente-MS Mar 5, 2024
75a8d0b
fixed docstring
orpuente-MS Mar 6, 2024
08761dc
changed push_lint macro to allow more control over the pushed span
orpuente-MS Mar 6, 2024
fde9b69
fixed typo in unit test
orpuente-MS Mar 6, 2024
5d09dca
Added redundant semicolons lint
orpuente-MS Mar 6, 2024
5c5d36b
added unit test for redundant semicolons
orpuente-MS Mar 6, 2024
1452400
add needless parens lint
orpuente-MS Mar 7, 2024
ea6d61e
add needless parens lint
orpuente-MS Mar 7, 2024
2095eff
fixed fmt
orpuente-MS Mar 7, 2024
76fb38e
fixed needless parens lint
orpuente-MS Mar 7, 2024
24918f2
less boilerplate
orpuente-MS Mar 9, 2024
17ffa4d
fixed crate docstring
orpuente-MS Mar 9, 2024
1e153d9
revert unnecesary name changes
orpuente-MS Mar 9, 2024
9b2e109
orpuente-MS Mar 9, 2024
f1e4ed1
Added infrastructure for user-personalized lint levels
orpuente-MS Mar 12, 2024
4273e0d
updated docstring to reflect API change
orpuente-MS Mar 12, 2024
8048624
Merge branch 'microsoft:main' into main
orpuente-MS Mar 13, 2024
6eb74ac
loading user-preferences from qsharp.json
orpuente-MS Mar 13, 2024
68bc4fb
Merge branch 'main' of https://github.com/orpuente-MS/qsharp
orpuente-MS Mar 13, 2024
3ecab31
reverted sample change
orpuente-MS Mar 13, 2024
bd86459
integrated lints_config into the qsharp.json file
orpuente-MS Mar 14, 2024
2fd7ad3
changed level name "warning" to "warn"
orpuente-MS Mar 14, 2024
36fe71a
Update compiler/qsc_linter/Cargo.toml
orpuente-MS Mar 14, 2024
eaa8efe
removed project clippy config since now it is set to in the workspace.
orpuente-MS Mar 14, 2024
9d24bad
removed the linter dependency in qsc_frontend
orpuente-MS Mar 14, 2024
e35e893
updated crate docstring
orpuente-MS Mar 14, 2024
e10764f
changed hir placeholder to LintLevel::Allow
orpuente-MS Mar 14, 2024
3de391d
added variant comment
orpuente-MS Mar 14, 2024
66f681b
added help messages to the lints
orpuente-MS Mar 14, 2024
81adad7
check for empty help
orpuente-MS Mar 14, 2024
2d41af2
.
orpuente-MS Mar 14, 2024
14bf1bd
orpuente-MS Mar 14, 2024
4a239db
Fixed precedence and set NeedlessParens to Allow
orpuente-MS Mar 15, 2024
24cf6a0
Merge branch 'microsoft:main' into main
orpuente-MS Mar 15, 2024
ae38f47
fixed typo in docstring
orpuente-MS Mar 15, 2024
de92e60
updated unit tests
orpuente-MS Mar 15, 2024
40156cc
Update samples/language/MultiFileProject/src/Main.qs
orpuente-MS Mar 18, 2024
927a22b
added unit-test to check that lints-config is being loaded
orpuente-MS Mar 18, 2024
ce603af
Merge branch 'main' of https://github.com/orpuente-MS/qsharp
orpuente-MS Mar 18, 2024
bc71993
simplified push_lint macro to lint
orpuente-MS Mar 18, 2024
6997f6d
Update wasm/src/project_system.rs
orpuente-MS Mar 18, 2024
a1776fb
Merge branch 'main' of https://github.com/orpuente-MS/qsharp
orpuente-MS Mar 18, 2024
1641373
moved Lint related structs back to linter crate (from qsc_data_structs)
orpuente-MS Mar 18, 2024
f833a4d
fixed unit test
orpuente-MS Mar 19, 2024
e7ba5f7
updated json schema
orpuente-MS Mar 19, 2024
d208971
Merge remote-tracking branch 'upstream/main'
orpuente-MS Mar 19, 2024
a7e6119
add lints to qsharp.schema.json
orpuente-MS Mar 19, 2024
6e1f6d1
remove unused dependecy to thiserror
orpuente-MS Mar 19, 2024
daf9614
remove serde Serialize from lints, we only need Desrialize
orpuente-MS Mar 19, 2024
1ef0c7a
orpuente-MS Mar 19, 2024
348553f
orpuente-MS Mar 19, 2024
d69ac28
fix test case in language_service
orpuente-MS Mar 19, 2024
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
24 changes: 24 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"compiler/qsc_fir",
"compiler/qsc_frontend",
"compiler/qsc_hir",
"compiler/qsc_linter",
"compiler/qsc_parse",
"compiler/qsc_passes",
"compiler/qsc_project",
Expand Down
1 change: 1 addition & 0 deletions compiler/qsc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ qsc_data_structures = { path = "../qsc_data_structures" }
qsc_doc_gen = { path = "../qsc_doc_gen" }
qsc_eval = { path = "../qsc_eval" }
qsc_frontend = { path = "../qsc_frontend" }
qsc_linter = { path = "../qsc_linter" }
qsc_ast = { path = "../qsc_ast" }
qsc_fir = { path = "../qsc_fir" }
qsc_hir = { path = "../qsc_hir" }
Expand Down
4 changes: 4 additions & 0 deletions compiler/qsc/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ pub enum ErrorKind {
/// These errors are typically related to optimization, transformation, code generation, passes,
/// and static analysis passes.
Pass(#[from] qsc_passes::Error),

/// `Lint` variant represents lints generated during the linting stage. These diagnostics are
/// typically emited from the language server and happens after all other compilation passes.
Lint(#[from] qsc_linter::Lint),
orpuente-MS marked this conversation as resolved.
Show resolved Hide resolved
}

#[must_use]
Expand Down
4 changes: 4 additions & 0 deletions compiler/qsc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,8 @@ pub use qsc_eval::{
state::{fmt_basis_state_label, fmt_complex, format_state_id, get_latex, get_phase},
};

pub mod linter {
pub use qsc_linter::{run_lints, LintConfig, LintKind, LintLevel};
}

pub use qsc_doc_gen::{display, generate_docs};
1 change: 1 addition & 0 deletions compiler/qsc_data_structures/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ repository.workspace = true
miette = { workspace = true }
serde = { workspace = true }
bitflags = { workspace = true }
thiserror = { workspace = true }

[dev-dependencies]
expect-test = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions compiler/qsc_data_structures/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ pub mod functors;
pub mod index_map;
pub mod language_features;
pub mod line_column;
pub mod linter;
pub mod span;
78 changes: 78 additions & 0 deletions compiler/qsc_data_structures/src/linter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use std::fmt::Display;

use miette::{Diagnostic, LabeledSpan};
use serde::{Deserialize, Serialize};

use crate::span::Span;

/// A lint emited by the linter.
#[derive(Debug, Clone, thiserror::Error)]
pub struct Lint {
/// A span indicating where the diagnostic is in the source code.
pub span: Span,
/// The lint level: allow, warning, error.
pub level: LintLevel,
/// The message the user will see in the code editor.
pub message: &'static str,
/// The help text the user will see in the code editor.
pub help: &'static str,
}

impl std::fmt::Display for Lint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}

impl Diagnostic for Lint {
fn severity(&self) -> Option<miette::Severity> {
match self.level {
LintLevel::Allow => None,
LintLevel::Warn | LintLevel::ForceWarn => Some(miette::Severity::Warning),
LintLevel::Error | LintLevel::ForceError => Some(miette::Severity::Error),
}
}

fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
let source_span = miette::SourceSpan::from(self.span);
let labeled_span = LabeledSpan::new_with_span(Some(self.to_string()), source_span);
Some(Box::new(vec![labeled_span].into_iter()))
}

fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
if self.help.is_empty() {
None
} else {
Some(Box::new(self.help))
}
}
}

/// A lint level. This defines if a lint will be treated as a warning or an error,
/// and if the lint level can be overriden by the user.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum LintLevel {
/// The lint is effectively disabled.
Allow,
/// The lint will be treated as a warning.
Warn,
/// The lint will be treated as a warning and cannot be overriden by the user.
ForceWarn,
/// The lint will be treated as an error.
Error,
/// The lint will be treated as an error and cannot be overriden by the user.
ForceError,
}

impl Display for LintLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let level = match self {
LintLevel::Allow => "",
LintLevel::Warn | LintLevel::ForceWarn => "warning",
LintLevel::Error | LintLevel::ForceError => "error",
};

write!(f, "{level}")
}
}
2 changes: 2 additions & 0 deletions compiler/qsc_frontend/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ pub struct Error(pub(super) ErrorKind);
pub(super) enum ErrorKind {
#[error("syntax error")]
Parse(#[from] qsc_parse::Error),
#[error(transparent)]
Lint(#[from] qsc_data_structures::linter::Lint),
#[error("name error")]
Resolve(#[from] resolve::Error),
#[error("type error")]
Expand Down
31 changes: 31 additions & 0 deletions compiler/qsc_linter/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
name = "qsc_linter"

version.workspace = true
authors.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true

[dependencies]
miette = { workspace = true }
qsc_ast = { path = "../qsc_ast" }
qsc_hir = { path = "../qsc_hir" }
qsc_data_structures = { path = "../qsc_data_structures" }
qsc_frontend = { path = "../qsc_frontend" }
serde = { workspace = true }
thiserror = { workspace = true }

[dev-dependencies]
expect-test = { workspace = true }
qsc_parse = { path = "../qsc_parse" }
serde_json = { workspace = true }
qsc = { path = "../qsc" }
qsc_passes = { path = "../qsc_passes" }

[lints]
workspace = true

[lib]
doctest = false
orpuente-MS marked this conversation as resolved.
Show resolved Hide resolved
70 changes: 70 additions & 0 deletions compiler/qsc_linter/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! This crate contains the linter for the Q# language.
//!
//! It includes lints for the following stages of the compilation process:
//! - AST
//! - HIR
//!
//! # Usage
//!
//! The entry points to the linter is the `run_lints` function, which takes
//! a [`qsc_frontend::compile::CompileUnit`] as input and outputs a [`Vec<Lint>`](Lint).
//!
//! ## Example
//!
//! ```
//! use linter::run_lints;;
//! use qsc::compile::compile;
//!
//! let unit: CompileUnit = compile(...);
//!
//! // The second argument is an optional user configuration.
//! let lints: Vec<Lint> = run_ast_lints(&package, None);
//! ```
//!
//! # How to add a new Lint
//!
//! We can add a new lint in two steps:
//! 1. Declaring the lint: here you set the lint name, the default [`LintLevel`], and the message the user will see.
//! 2. Implementing the lint: here you write the pattern matching logic of the new lint.
//!
//! Below is a full example of how to a new AST lint.
//!
//! ## Example
//!
//! First, we add our lint to `src/lints/ast.rs`.
//! ```
//! declare_ast_lints!{
//! ...
//! (DoubleParens, LintLevel::Warn, "unnecesary double parentheses"),
//! }
//! ```
//!
//! Then we implement the right `LintPass` for our new lint, in this case `linter::ast::AstLintPass`
//! ```
//! impl linter::ast::AstLintPass for DoubleParens {
//! // we only need to impl the relevant check_* method, all the other ones
//! // will default to an empty method that will get optmized by rust
//! fn check_expr(expr: &qsc_ast::ast::Expr, buffer: &mut Vec<Lint>) {
//! // we match the relevant pattern
//! if let ExprKind::Paren(ref inner_expr) = *expr.kind {
//! if matches!(*inner_expr.kind, ExprKind::Paren(_)) {
//! // we push the lint to the buffer
//! push_lint!(Self, expr.span, buffer);
//! }
//! }
//! }
//! }
//! ```

#![deny(missing_docs)]

mod linter;
mod lints;
#[cfg(test)]
mod tests;

pub use linter::{run_lints, LintConfig, LintKind};
pub use qsc_data_structures::linter::{Lint, LintLevel};
48 changes: 48 additions & 0 deletions compiler/qsc_linter/src/linter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

pub(crate) mod ast;
pub(crate) mod hir;

use crate::lints::{ast::AstLint, hir::HirLint};
use qsc_data_structures::linter::{Lint, LintLevel};
use qsc_frontend::compile::CompileUnit;
use serde::{Deserialize, Serialize};

use self::{ast::run_ast_lints, hir::run_hir_lints};

/// The entry point to the linter. It takes a [`qsc_frontend::compile::CompileUnit`]
/// as input and outputs a [`Vec<Lint>`](Lint).
#[must_use]
pub fn run_lints(compile_unit: &CompileUnit, config: Option<&[LintConfig]>) -> Vec<Lint> {
let mut ast_lints = run_ast_lints(&compile_unit.ast.package, config);
let mut hir_lints = run_hir_lints(&compile_unit.package, config);

let mut lints = Vec::new();
lints.append(&mut ast_lints);
lints.append(&mut hir_lints);
lints
.into_iter()
.filter(|lint| !matches!(lint.level, LintLevel::Allow))
.collect()
}

/// End-user configuration for each lint level.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LintConfig {
#[serde(rename = "lint")]
/// Represents the lint name.
pub kind: LintKind,
/// The lint level.
pub level: LintLevel,
}

/// Represents a lint name.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(untagged)]
pub enum LintKind {
/// AST lint name.
Ast(AstLint),
/// HIR lint name.
Hir(HirLint),
}
Loading
Loading