Skip to content

Commit 15e59eb

Browse files
committed
chore: merge main
2 parents 60c3f45 + 31cbb5d commit 15e59eb

File tree

26 files changed

+365
-211
lines changed

26 files changed

+365
-211
lines changed

crates/pglt_analyser/CONTRIBUTING.md

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ We follow a naming convention according to what the rule does:
1717
1. Forbid a concept
1818

1919
```block
20-
no<Concept>
20+
ban<Concept>
2121
```
2222

23-
When a rule's sole intention is to **forbid a single concept** the rule should be named using the `no` prefix.
23+
When a rule's sole intention is to **forbid a single concept** the rule should be named using the `ban` prefix.
2424

25-
1. Mandate a concept
25+
Example: "banDropColumn"
26+
27+
2. Mandate a concept
2628

2729
```block
2830
use<Concept>
@@ -35,9 +37,10 @@ We follow a naming convention according to what the rule does:
3537
A rule should be informative to the user, and give as much explanation as possible.
3638

3739
When writing a rule, you must adhere to the following **pillars**:
40+
3841
1. Explain to the user the error. Generally, this is the message of the diagnostic.
39-
1. Explain to the user **why** the error is triggered. Generally, this is implemented with an additional node.
40-
1. Tell the user what they should do. Generally, this is implemented using a code action. If a code action is not applicable a note should tell the user what they should do to fix the error.
42+
2. Explain to the user **why** the error is triggered. Generally, this is implemented with an additional node.
43+
3. Tell the user what they should do. Generally, this is implemented using a code action. If a code action is not applicable a note should tell the user what they should do to fix the error.
4144

4245
### Create and implement the rule
4346

@@ -53,10 +56,11 @@ Let's say we want to create a new **lint** rule called `useMyRuleName`, follow t
5356
```shell
5457
just new-lintrule safety useMyRuleName
5558
```
59+
5660
The script will generate a bunch of files inside the `pglt_analyser` crate.
5761
Among the other files, you'll find a file called `use_my_new_rule_name.rs` inside the `pglt_analyser/lib/src/lint/safety` folder. You'll implement your rule in this file.
5862

59-
1. The `Option` type doesn't have to be used, so it can be considered optional. However, it has to be defined as `type Option = ()`.
63+
1. The `Options` type doesn't have to be used, so it can be considered optional. However, it has to be defined as `type Options = ()`.
6064
1. Implement the `run` function: The function is called for every statement, and should return zero or more diagnostics. Follow the [pillars](#explain-a-rule-to-the-user) when writing the message of a diagnostic
6165

6266
Don't forget to format your code with `just f` and lint with `just l`.
@@ -107,7 +111,7 @@ pub enum Behavior {
107111
```
108112

109113
Note that we use a boxed slice `Box<[Box<str>]>` instead of `Vec<String>`.
110-
This allows saving memory: [boxed slices and boxed str use 2 words instead of three words](https://nnethercote.github.io/perf-book/type-sizes.html#boxed-slices).
114+
This allows saving memory: [boxed slices and boxed str use two instead of three words](https://nnethercote.github.io/perf-book/type-sizes.html#boxed-slices).
111115

112116
With these types in place, you can set the associated type `Options` of the rule:
113117

@@ -127,6 +131,7 @@ The compiler should warn you that `MyRuleOptions` does not implement some requir
127131
We currently require implementing _serde_'s traits `Deserialize`/`Serialize`.
128132

129133
Also, we use other `serde` macros to adjust the JSON configuration:
134+
130135
- `rename_all = "snake_case"`: it renames all fields in camel-case, so they are in line with the naming style of the `pglsp.toml`.
131136
- `deny_unknown_fields`: it raises an error if the configuration contains extraneous fields.
132137
- `default`: it uses the `Default` value when the field is missing from `pglsp.toml`. This macro makes the field optional.
@@ -159,7 +164,6 @@ pub enum Behavior {
159164

160165
Below, there are many tips and guidelines on how to create a lint rule using our infrastructure.
161166

162-
163167
#### `declare_lint_rule`
164168

165169
This macro is used to declare an analyzer rule type, and implement the [RuleMeta] trait for it.
@@ -235,6 +239,7 @@ impl Rule for BanDropColumn {
235239
### Document the rule
236240

237241
The documentation needs to adhere to the following rules:
242+
238243
- The **first** paragraph of the documentation is used as brief description of the rule, and it **must** be written in one single line. Breaking the paragraph in multiple lines will break the table content of the rules page.
239244
- The next paragraphs can be used to further document the rule with as many details as you see fit.
240245
- The documentation must have a `## Examples` header, followed by two headers: `### Invalid` and `### Valid`. `### Invalid` must go first because we need to show when the rule is triggered.
@@ -246,7 +251,7 @@ The documentation needs to adhere to the following rules:
246251

247252
Here's an example of how the documentation could look like:
248253

249-
```rust
254+
````rust
250255
declare_lint_rule! {
251256
/// Dropping a column may break existing clients.
252257
///
@@ -269,7 +274,7 @@ declare_lint_rule! {
269274
sources: &[RuleSource::Squawk("ban-drop-column")],
270275
}
271276
}
272-
```
277+
````
273278

274279
This will cause the documentation generator to ensure the rule does emit
275280
exactly one diagnostic for this code, and to include a snapshot for the
@@ -294,15 +299,14 @@ Stage and commit your changes:
294299
> git commit -m 'feat(pglt_analyser): myRuleName'
295300
```
296301

297-
298302
### Deprecate a rule
299303

300304
There are occasions when a rule must be deprecated, to avoid breaking changes. The reason
301305
of deprecation can be multiple.
302306

303307
In order to do, the macro allows adding additional field to add the reason for deprecation
304308

305-
```rust
309+
````rust
306310
use pglt_analyse::declare_lint_rule;
307311

308312
declare_lint_rule! {
@@ -328,5 +332,4 @@ declare_lint_rule! {
328332
sources: &[RuleSource::Squawk("ban-drop-column")],
329333
}
330334
}
331-
```
332-
335+
````

crates/pglt_analyser/src/lint/safety.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
33
use pglt_analyse::declare_lint_group;
44
pub mod ban_drop_column;
5-
declare_lint_group! { pub Safety { name : "safety" , rules : [self :: ban_drop_column :: BanDropColumn ,] } }
5+
pub mod ban_drop_not_null;
6+
declare_lint_group! { pub Safety { name : "safety" , rules : [self :: ban_drop_column :: BanDropColumn , self :: ban_drop_not_null :: BanDropNotNull ,] } }

crates/pglt_analyser/src/lint/safety/ban_drop_column.rs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
use pglt_analyse::{context::RuleContext, declare_lint_rule, Rule, RuleDiagnostic, RuleSource};
22
use pglt_console::markup;
33

4-
#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
5-
// #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
6-
#[serde(rename_all = "camelCase", deny_unknown_fields, default)]
7-
pub struct Options {
8-
test: String,
9-
}
10-
114
declare_lint_rule! {
125
/// Dropping a column may break existing clients.
136
///
@@ -19,7 +12,7 @@ declare_lint_rule! {
1912
///
2013
/// ### Invalid
2114
///
22-
/// ```sql,expect_diagnostic
15+
/// ```sql,ignore
2316
/// alter table test drop column id;
2417
/// ```
2518
///
@@ -32,7 +25,7 @@ declare_lint_rule! {
3225
}
3326

3427
impl Rule for BanDropColumn {
35-
type Options = Options;
28+
type Options = ();
3629

3730
fn run(ctx: &RuleContext<Self>) -> Vec<RuleDiagnostic> {
3831
let mut diagnostics = Vec::new();
@@ -47,7 +40,7 @@ impl Rule for BanDropColumn {
4740
markup! {
4841
"Dropping a column may break existing clients."
4942
},
50-
).detail(None, format!("[{}] You can leave the column as nullable or delete the column once queries no longer select or modify the column.", ctx.options().test)));
43+
).detail(None, "You can leave the column as nullable or delete the column once queries no longer select or modify the column."));
5144
}
5245
}
5346
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use pglt_analyse::{context::RuleContext, declare_lint_rule, Rule, RuleDiagnostic, RuleSource};
2+
use pglt_console::markup;
3+
4+
declare_lint_rule! {
5+
/// Dropping a NOT NULL constraint may break existing clients.
6+
///
7+
/// Application code or code written in procedural languages like PL/SQL or PL/pgSQL may not expect NULL values for the column that was previously guaranteed to be NOT NULL and therefore may fail to process them correctly.
8+
///
9+
/// You can consider using a marker value that represents NULL. Alternatively, create a new table allowing NULL values, copy the data from the old table, and create a view that filters NULL values.
10+
///
11+
/// ## Examples
12+
///
13+
/// ### Invalid
14+
///
15+
/// ```sql,ignore
16+
/// alter table users alter column email drop not null;
17+
/// ```
18+
pub BanDropNotNull {
19+
version: "next",
20+
name: "banDropNotNull",
21+
recommended: true,
22+
sources: &[RuleSource::Squawk("ban-drop-not-null")],
23+
24+
}
25+
}
26+
27+
impl Rule for BanDropNotNull {
28+
type Options = ();
29+
30+
fn run(ctx: &RuleContext<Self>) -> Vec<RuleDiagnostic> {
31+
let mut diagnostics = Vec::new();
32+
33+
if let pglt_query_ext::NodeEnum::AlterTableStmt(stmt) = &ctx.stmt() {
34+
for cmd in &stmt.cmds {
35+
if let Some(pglt_query_ext::NodeEnum::AlterTableCmd(cmd)) = &cmd.node {
36+
if cmd.subtype() == pglt_query_ext::protobuf::AlterTableType::AtDropNotNull {
37+
diagnostics.push(RuleDiagnostic::new(
38+
rule_category!(),
39+
None,
40+
markup! {
41+
"Dropping a NOT NULL constraint may break existing clients."
42+
},
43+
).detail(None, "Consider using a marker value that represents NULL. Alternatively, create a new table allowing NULL values, copy the data from the old table, and create a view that filters NULL values."));
44+
}
45+
}
46+
}
47+
}
48+
49+
diagnostics
50+
}
51+
}

crates/pglt_analyser/src/options.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33
use crate::lint;
44
pub type BanDropColumn =
55
<lint::safety::ban_drop_column::BanDropColumn as pglt_analyse::Rule>::Options;
6+
pub type BanDropNotNull =
7+
<lint::safety::ban_drop_not_null::BanDropNotNull as pglt_analyse::Rule>::Options;

crates/pglt_cli/src/cli_options.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ pub struct CliOptions {
1818
#[bpaf(long("use-server"), switch, fallback(false))]
1919
pub use_server: bool,
2020

21+
/// Skip connecting to the database and only run checks that don't require a database connection.
22+
#[bpaf(long("skip-db"), switch, fallback(false))]
23+
pub skip_db: bool,
24+
2125
/// Print additional diagnostics, and some diagnostics show more information. Also, print out what files were processed and which ones were modified.
2226
#[bpaf(long("verbose"), switch, fallback(false))]
2327
pub verbose: bool,

crates/pglt_cli/src/commands/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ pub enum PgltCommand {
3535
Check {
3636
#[bpaf(external(partial_configuration), hide_usage, optional)]
3737
configuration: Option<PartialConfiguration>,
38+
3839
#[bpaf(external, hide_usage)]
3940
cli_options: CliOptions,
41+
4042
/// Use this option when you want to format code piped from `stdin`, and print the output to `stdout`.
4143
///
4244
/// The file doesn't need to exist on disk, what matters is the extension of the file. Based on the extension, we know how to check the code.
@@ -286,6 +288,7 @@ pub(crate) trait CommandRunner: Sized {
286288
configuration,
287289
vcs_base_path,
288290
gitignore_matches,
291+
skip_db: cli_options.skip_db,
289292
})?;
290293

291294
let execution = self.get_execution(cli_options, console, workspace)?;

crates/pglt_cli/src/execute/process_file/check.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub(crate) fn check_with_guard<'ctx>(
2828
let (only, skip) = (Vec::new(), Vec::new());
2929

3030
let max_diagnostics = ctx.remaining_diagnostics.load(Ordering::Relaxed);
31+
3132
let pull_diagnostics_result = workspace_file
3233
.guard()
3334
.pull_diagnostics(

crates/pglt_cli/src/execute/traverse.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ pub(crate) fn traverse(
123123
let skipped = skipped.load(Ordering::Relaxed);
124124
let suggested_fixes_skipped = printer.skipped_fixes();
125125
let diagnostics_not_printed = printer.not_printed_diagnostics();
126+
126127
Ok(TraverseResult {
127128
summary: TraversalSummary {
128129
changed,
@@ -381,6 +382,7 @@ impl<'ctx> DiagnosticsPrinter<'ctx> {
381382
}
382383
}
383384
}
385+
384386
diagnostics_to_print
385387
}
386388
}

crates/pglt_cli/src/reporter/gitlab.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ use std::{
1313
};
1414

1515
pub struct GitLabReporter {
16-
pub execution: Execution,
17-
pub diagnostics: DiagnosticsPayload,
16+
pub(crate) execution: Execution,
17+
pub(crate) diagnostics: DiagnosticsPayload,
1818
}
1919

2020
impl Reporter for GitLabReporter {

0 commit comments

Comments
 (0)