Skip to content

Commit

Permalink
feat(linter): add line-length rule (#232)
Browse files Browse the repository at this point in the history
  • Loading branch information
felipeasimos authored Feb 20, 2025
1 parent 395dd28 commit 83d016d
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 2 deletions.
36 changes: 36 additions & 0 deletions docs/rules/line-length.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# `line-length`

> Category: style
>
> Enabled by default?: No
## What This Rule Does

Checks if any line goes beyond a given number of columns.

## Examples

Examples of **incorrect** code for this rule (with a threshold of 120 columns):

```zig
const std = @import("std");
const longStructDeclarationInOneLine = struct { max_length: u32 = 120, a: usize = 123, b: usize = 12354, c: usize = 1234352 };
fn reallyExtraVerboseFunctionNameToThePointOfBeingACodeSmellAndProbablyAHintThatYouCanGetAwayWithAnotherNameOrSplittingThisIntoSeveralFunctions() u32 {
return 123;
}
```

Examples of **correct** code for this rule (with a threshold of 120 columns):

```zig
const std = @import("std");
const longStructInMultipleLines = struct {
max_length: u32 = 120,
a: usize = 123,
b: usize = 12354,
c: usize = 1234352,
};
fn Get123Constant() u32 {
return 123;
}
```
2 changes: 1 addition & 1 deletion src/linter/config/rule_config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub fn RuleConfig(RuleImpl: type) type {
return Self{ .severity = severity, .rule_impl = rule_impl };
}
pub fn rule(self: *Self) Rule {
const rule_impl: *RuleImpl = @ptrCast(@constCast(self.rule_impl));
const rule_impl: *RuleImpl = @ptrCast(@alignCast(@constCast(self.rule_impl)));
return rule_impl.rule();
}
};
Expand Down
1 change: 1 addition & 0 deletions src/linter/config/rules_config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ pub const RulesConfig = struct {
unsafe_undefined: RuleConfig(rules.UnsafeUndefined) = .{},
unused_decls: RuleConfig(rules.UnusedDecls) = .{},
must_return_ref: RuleConfig(rules.MustReturnRef) = .{},
line_length: RuleConfig(rules.LineLength) = .{},
};
5 changes: 4 additions & 1 deletion src/linter/rule.zig
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const VTable = struct {
/// ## Creating Rules
/// Rules are structs (or anything else that can have methods) that have 1 or
/// more of the following methods:
/// - `runOnce(*self, *ctx)`
/// - `runOnNode(*self, node, *ctx)`
/// - `runOnSymbol(*self, symbol, *ctx)`
/// `Rule` provides a uniform interface to `Linter`. `Rule.init` will look for
Expand All @@ -45,6 +46,7 @@ pub const Rule = struct {
id: Id,
ptr: *anyopaque,
vtable: VTable,
// runOnceFn: RunOnceFn,
// runOnNodeFn: RunOnNodeFn,
// runOnSymbolFn: RunOnSymbolFn,

Expand All @@ -70,6 +72,7 @@ pub const Rule = struct {
suspicious,
restriction,
pedantic,
style,
};

pub const WithSeverity = struct {
Expand Down Expand Up @@ -100,7 +103,7 @@ pub const Rule = struct {
const gen = struct {
pub fn runOnce(pointer: *const anyopaque, ctx: *LinterContext) anyerror!void {
if (@hasDecl(ptr_info.child, "runOnce")) {
const self: T = @ptrCast(@constCast(pointer));
const self: T = @ptrCast(@alignCast(@constCast(pointer)));
return ptr_info.child.runOnce(self, ctx);
}
}
Expand Down
1 change: 1 addition & 0 deletions src/linter/rules.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ pub const SuppressedErrors = @import("./rules/suppressed_errors.zig");
pub const UnsafeUndefined = @import("./rules/unsafe_undefined.zig");
pub const UnusedDecls = @import("./rules/unused_decls.zig");
pub const MustReturnRef = @import("./rules/must_return_ref.zig");
pub const LineLength = @import("./rules/line_length.zig");
125 changes: 125 additions & 0 deletions src/linter/rules/line_length.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//! ## What This Rule Does
//!
//! Checks if any line goes beyond a given number of columns.
//!
//! ## Examples
//!
//! Examples of **incorrect** code for this rule (with a threshold of 120 columns):
//! ```zig
//! const std = @import("std");
//! const longStructDeclarationInOneLine = struct { max_length: u32 = 120, a: usize = 123, b: usize = 12354, c: usize = 1234352 };
//! fn reallyExtraVerboseFunctionNameToThePointOfBeingACodeSmellAndProbablyAHintThatYouCanGetAwayWithAnotherNameOrSplittingThisIntoSeveralFunctions() u32 {
//! return 123;
//! }
//! ```
//!
//! Examples of **correct** code for this rule (with a threshold of 120 columns):
//! ```zig
//! const std = @import("std");
//! const longStructInMultipleLines = struct {
//! max_length: u32 = 120,
//! a: usize = 123,
//! b: usize = 12354,
//! c: usize = 1234352,
//! };
//! fn Get123Constant() u32 {
//! return 123;
//! }
//! ```

const std = @import("std");
const _rule = @import("../rule.zig");
const span = @import("../../span.zig");

const LinterContext = @import("../lint_context.zig");
const Rule = _rule.Rule;
const Error = @import("../../Error.zig");
const LabeledSpan = span.LabeledSpan;

max_length: u32 = 120,

const LineLength = @This();
pub const meta: Rule.Meta = .{
.name = "line-length",
.category = .style,
.default = .off,
};

pub fn lineLengthDiagnostic(ctx: *LinterContext, line_start: u32, line_length: u32) Error {
return ctx.diagnosticf(
"line length of {} characters is too big.",
.{line_length},
.{LabeledSpan.unlabeled(line_start, line_start + line_length)},
);
}

pub fn getNewlineOffset(line: []const u8) u32 {
if (line.len > 1 and line[line.len - 2] == '\r') {
return 2;
}
return 1;
}

pub fn runOnce(self: *const LineLength, ctx: *LinterContext) void {
var line_start_idx: u32 = 0;
var lines = std.mem.splitSequence(u8, ctx.source.text(), "\n");
const newline_offset = getNewlineOffset(lines.first());
lines.reset();
while (lines.next()) |line| {
const line_length = @as(u32, @intCast(line.len));
if (line.len > self.max_length) {
ctx.report(lineLengthDiagnostic(ctx, line_start_idx, line_length));
}
line_start_idx += line_length + newline_offset;
}
}

pub fn rule(self: *LineLength) Rule {
return Rule.init(self);
}

const RuleTester = @import("../tester.zig");
test LineLength {
const t = std.testing;

var line_length = LineLength{};
var runner = RuleTester.init(t.allocator, line_length.rule());
defer runner.deinit();

const pass = &[_][:0]const u8{
\\const std = @import("std");
\\fn foo() std.mem.Allocator.Error!void {
\\ _ = try std.heap.page_allocator.alloc(u8, 8);
\\}
,
\\const std = @import("std");
\\const longStructInMultipleLines = struct {
\\ max_length: u32 = 120,
\\ a: usize = 123,
\\ b: usize = 12354,
\\ c: usize = 1234352,
\\};
\\fn Get123Constant() u32 {
\\ return 123;
\\}
};

const fail = &[_][:0]const u8{
\\const std = @import("std");
\\fn foo() std.mem.Allocator.Error!void {
\\ // ok so this is a super unnecessary line that is artificially being made long through this self-referential comment thats keeps on going until hitting a number of columns that violates the rule
\\ _ = try std.heap.page_allocator.alloc(u8, 8);
\\}
,
\\const std = @import("std");
\\const longStructDeclarationInOneLine = struct { max_length: u32 = 120, a: usize = 123, b: usize = 12354, c: usize = 1234352 };
\\fn reallyExtraVerboseFunctionNameToThePointOfBeingACodeSmellAndProbablyAHintThatYouCanGetAwayWithAnotherNameOrSplittingThisIntoSeveralFunctions() u32 {
\\ return 123;
\\}
};

try runner
.withPass(pass)
.withFail(fail)
.run();
}
24 changes: 24 additions & 0 deletions src/linter/rules/snapshots/line-length.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
𝙭 line-length: line length of 196 characters is too big.
╭─[line-length.zig:3:1]
2 β”‚ fn foo() std.mem.Allocator.Error!void {
3 β”‚ // ok so this is a super unnecessary line that is artificially being made long through this self-referential comment thats keeps on going until hitting a number of columns that violates the rule
Β· ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
4 β”‚ _ = try std.heap.page_allocator.alloc(u8, 8);
╰────

𝙭 line-length: line length of 126 characters is too big.
╭─[line-length.zig:2:1]
1 β”‚ const std = @import("std");
2 β”‚ const longStructDeclarationInOneLine = struct { max_length: u32 = 120, a: usize = 123, b: usize = 12354, c: usize = 1234352 };
Β· ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
3 β”‚ fn reallyExtraVerboseFunctionNameToThePointOfBeingACodeSmellAndProbablyAHintThatYouCanGetAwayWithAnotherNameOrSplittingThisIntoSeveralFunctions() u32 {
╰────

𝙭 line-length: line length of 151 characters is too big.
╭─[line-length.zig:3:1]
2 β”‚ const longStructDeclarationInOneLine = struct { max_length: u32 = 120, a: usize = 123, b: usize = 12354, c: usize = 1234352 };
3 β”‚ fn reallyExtraVerboseFunctionNameToThePointOfBeingACodeSmellAndProbablyAHintThatYouCanGetAwayWithAnotherNameOrSplittingThisIntoSeveralFunctions() u32 {
Β· ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
4 β”‚ return 123;
╰────

0 comments on commit 83d016d

Please sign in to comment.