{ "type": "object", "properties": { "rules": { "$ref": "#/definitions/linter.config.rules_config.RulesConfig" }, "ignore": { "type": "array", "description": "Files and folders to skip. Uses `startsWith` to check if files are ignored.\n\n`zig-out` and `vendor` are always ignored, as well as hidden folders.", "default": [ "vendor", "zig-out" ], "items": { "type": "string" } } }, "$schema": "https://json-schema.org/draft-07/schema#", "definitions": { "linter.rules.allocator_first_param": { "type": "object", "title": "allocator-first-param config", "properties": { "ignore": { "type": "array", "default": [], "items": { "type": "string" } } } }, "linter.rules.avoid_as": { "type": "object", "title": "avoid-as config", "properties": {} }, "linter.rules.suppressed_errors": { "type": "object", "title": "suppressed-errors config", "properties": {} }, "linter.rules.unused_decls": { "type": "object", "title": "unused-decls config", "properties": {} }, "linter.rules.useless_error_return": { "type": "object", "title": "useless-error-return config", "properties": {} }, "Error.Severity": { "title": "Severity", "description": "Set the error level of a rule. 'off' and 'allow' do the same thing.", "oneOf": [ { "type": "integer", "minimum": 0, "maximum": 2 }, { "type": "string", "enum": [ "off", "deny", "warn", "error", "allow" ] } ] }, "linter.config.rules_config.RulesConfig": { "type": "object", "description": "Configure which rules are enabled and how.", "properties": { "avoid-as": { "description": "## What This Rule Does\n\nDisallows using `@as()` when types can be otherwise inferred.\n\nZig has powerful [Result Location Semantics](https://ziglang.org/documentation/master/#Result-Location-Semantics) for inferring what type\nsomething should be. This happens in function parameters, return types,\nand type annotations. `@as()` is a last resort when no other contextual\ninformation is available. In any other case, other type inference mechanisms\nshould be used.\n\n:::warning\n\nChecks for function parameters and return types are not yet implemented.\n\n:::\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nconst x = @as(u32, 1);\n\nfn foo(x: u32) u64 {\n return @as(u64, x); // type is inferred from return type\n}\nfoo(@as(u32, 1)); // type is inferred from function signature\n```\n\nExamples of **correct** code for this rule:\n```zig\nconst x: u32 = 1;\n\nfn foo(x: u32) void {\n // ...\n}\nfoo(1);\n```", "default": "warn", "markdownDescription": "## What This Rule Does\n\nDisallows using `@as()` when types can be otherwise inferred.\n\nZig has powerful [Result Location Semantics](https://ziglang.org/documentation/master/#Result-Location-Semantics) for inferring what type\nsomething should be. This happens in function parameters, return types,\nand type annotations. `@as()` is a last resort when no other contextual\ninformation is available. In any other case, other type inference mechanisms\nshould be used.\n\n:::warning\n\nChecks for function parameters and return types are not yet implemented.\n\n:::\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nconst x = @as(u32, 1);\n\nfn foo(x: u32) u64 {\n return @as(u64, x); // type is inferred from return type\n}\nfoo(@as(u32, 1)); // type is inferred from function signature\n```\n\nExamples of **correct** code for this rule:\n```zig\nconst x: u32 = 1;\n\nfn foo(x: u32) void {\n // ...\n}\nfoo(1);\n```", "oneOf": [ { "$ref": "#/definitions/Error.Severity" }, { "type": "array", "items": false, "prefixItems": [ { "$ref": "#/definitions/Error.Severity" }, { "$ref": "#/definitions/linter.rules.avoid_as" } ] } ] }, "case-convention": { "description": "## What This Rule Does\nEnforces Zig's naming convention.\n\n:::warning\nOnly functions are checked at this time.\n:::\n\n## Functions\nIn general, functions that return values should use camelCase names while\nthose that return types should use PascalCase. Specially coming from Rust,\nsome people may be used to use snake_case for their functions, which can\nlead to inconsistencies in the code.\n\nNote that `extern` functions are not checked since you cannot change\ntheir names.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nfn this_one_is_in_snake_case() void {}\nfn generic(T: type) T { return T{}; }\n```\n\nExamples of **correct** code for this rule:\n```zig\nfn thisFunctionIsInCamelCase() void {}\nfn Generic(T: type) T { return T{}; }\nextern fn this_is_declared_in_c() void;\n```", "default": "off", "markdownDescription": "## What This Rule Does\nEnforces Zig's naming convention.\n\n:::warning\nOnly functions are checked at this time.\n:::\n\n## Functions\nIn general, functions that return values should use camelCase names while\nthose that return types should use PascalCase. Specially coming from Rust,\nsome people may be used to use snake_case for their functions, which can\nlead to inconsistencies in the code.\n\nNote that `extern` functions are not checked since you cannot change\ntheir names.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nfn this_one_is_in_snake_case() void {}\nfn generic(T: type) T { return T{}; }\n```\n\nExamples of **correct** code for this rule:\n```zig\nfn thisFunctionIsInCamelCase() void {}\nfn Generic(T: type) T { return T{}; }\nextern fn this_is_declared_in_c() void;\n```", "oneOf": [ { "$ref": "#/definitions/Error.Severity" }, { "type": "array", "items": false, "prefixItems": [ { "$ref": "#/definitions/Error.Severity" }, { "$ref": "#/definitions/linter.rules.case_convention" } ] } ] }, "allocator-first-param": { "description": "## What This Rule Does\n\nChecks that functions taking allocators as parameters have the allocator as\nthe first parameter. This conforms to common Zig conventions.\n\n## Rule Details\nThis rule looks for functions take an `Allocator` parameter and reports a\nviolation if\n- it is not the first parameter, or\n- there is a `self` parameter, and the allocator does not immediately follow it.\n\nParameters are considered to be an allocator if\n- the are named `allocator`, `alloc`, `gpa`, or `arena`, or one of those\n with leading/trailing underscores,\n- their type ends with `Allocator`\n\nParameters are considered to be a `self` parameter if\n- they are named `self`, `this`, or one of those with leading/trailing underscores.\n- their type is `@This()`, `*@This()`, etc.\n- their type is a Capitalized and the function is within the definition of a\n similarly named container (e.g. a struct).\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nfn foo(x: u32, allocator: Allocator) !*u32 {\n const heap_x = try allocator.create(u32);\n heap_x.* = x;\n return heap_x;\n}\n```\n\nExamples of **correct** code for this rule:\n```zig\nfn foo(allocator: Allocator, x: u32) !*u32 {\n const heap_x = try allocator.create(u32);\n heap_x.* = x;\n return heap_x;\n}\nconst Foo = struct {\n list: std.ArrayListUnmanaged(u32) = .{},\n // when writing methods, `self` must be the first parameter\n pub fn expandCapacity(self: *Foo, allocator: Allocator, new_len: usize) !void {\n try self.list.ensureTotalCapacity(allocator, new_len);\n }\n};\n```", "default": "off", "markdownDescription": "## What This Rule Does\n\nChecks that functions taking allocators as parameters have the allocator as\nthe first parameter. This conforms to common Zig conventions.\n\n## Rule Details\nThis rule looks for functions take an `Allocator` parameter and reports a\nviolation if\n- it is not the first parameter, or\n- there is a `self` parameter, and the allocator does not immediately follow it.\n\nParameters are considered to be an allocator if\n- the are named `allocator`, `alloc`, `gpa`, or `arena`, or one of those\n with leading/trailing underscores,\n- their type ends with `Allocator`\n\nParameters are considered to be a `self` parameter if\n- they are named `self`, `this`, or one of those with leading/trailing underscores.\n- their type is `@This()`, `*@This()`, etc.\n- their type is a Capitalized and the function is within the definition of a\n similarly named container (e.g. a struct).\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nfn foo(x: u32, allocator: Allocator) !*u32 {\n const heap_x = try allocator.create(u32);\n heap_x.* = x;\n return heap_x;\n}\n```\n\nExamples of **correct** code for this rule:\n```zig\nfn foo(allocator: Allocator, x: u32) !*u32 {\n const heap_x = try allocator.create(u32);\n heap_x.* = x;\n return heap_x;\n}\nconst Foo = struct {\n list: std.ArrayListUnmanaged(u32) = .{},\n // when writing methods, `self` must be the first parameter\n pub fn expandCapacity(self: *Foo, allocator: Allocator, new_len: usize) !void {\n try self.list.ensureTotalCapacity(allocator, new_len);\n }\n};\n```", "oneOf": [ { "$ref": "#/definitions/Error.Severity" }, { "type": "array", "items": false, "prefixItems": [ { "$ref": "#/definitions/Error.Severity" }, { "$ref": "#/definitions/linter.rules.allocator_first_param" } ] } ] }, "useless-error-return": { "description": "## What This Rule Does\n\nDetects functions that have an error union return type but never actually return an error.\nThis can happen in two ways:\n1. The function never returns an error value\n2. The function catches all errors internally and never propagates them to the caller\n\nHaving an error union return type when errors are never returned makes the code less clear\nand forces callers to handle errors that will never occur.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\n// Function declares error return but only returns void\nfn foo() !void {\n return;\n}\n\n// Function catches all errors internally\npub fn init(allocator: std.mem.Allocator) !Foo {\n const new = allocator.create(Foo) catch @panic(\"OOM\");\n new.* = .{};\n return new;\n}\n\n// Function only returns success value\nfn bar() !void {\n const e = baz();\n return e;\n}\n```\n\nExamples of **correct** code for this rule:\n```zig\n// Function properly propagates errors\nfn foo() !void {\n return error.Oops;\n}\n\n// Function returns result of fallible operation\nfn bar() !void {\n return baz();\n}\n\n// Function propagates caught errors\nfn qux() !void {\n bar() catch |e| return e;\n}\n\n// Function with conditional error return\nfn check(x: bool) !void {\n return if (x) error.Invalid else {};\n}\n\n// Empty error set is explicitly allowed\nfn noErrors() error{}!void {}\n```", "default": "off", "markdownDescription": "## What This Rule Does\n\nDetects functions that have an error union return type but never actually return an error.\nThis can happen in two ways:\n1. The function never returns an error value\n2. The function catches all errors internally and never propagates them to the caller\n\nHaving an error union return type when errors are never returned makes the code less clear\nand forces callers to handle errors that will never occur.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\n// Function declares error return but only returns void\nfn foo() !void {\n return;\n}\n\n// Function catches all errors internally\npub fn init(allocator: std.mem.Allocator) !Foo {\n const new = allocator.create(Foo) catch @panic(\"OOM\");\n new.* = .{};\n return new;\n}\n\n// Function only returns success value\nfn bar() !void {\n const e = baz();\n return e;\n}\n```\n\nExamples of **correct** code for this rule:\n```zig\n// Function properly propagates errors\nfn foo() !void {\n return error.Oops;\n}\n\n// Function returns result of fallible operation\nfn bar() !void {\n return baz();\n}\n\n// Function propagates caught errors\nfn qux() !void {\n bar() catch |e| return e;\n}\n\n// Function with conditional error return\nfn check(x: bool) !void {\n return if (x) error.Invalid else {};\n}\n\n// Empty error set is explicitly allowed\nfn noErrors() error{}!void {}\n```", "oneOf": [ { "$ref": "#/definitions/Error.Severity" }, { "type": "array", "items": false, "prefixItems": [ { "$ref": "#/definitions/Error.Severity" }, { "$ref": "#/definitions/linter.rules.useless_error_return" } ] } ] }, "unused-decls": { "description": "## What This Rule Does\n\nDisallows container-scoped variables that are declared but never used. Note\nthat top-level declarations are included.\n\nThe Zig compiler checks for unused parameters, payloads bound by `if`,\n`catch`, etc, and `const`/`var` declaration within functions. However,\nvariables and functions declared in container scopes are not given the same\ntreatment. This rule handles those cases.\n\n:::warning\n\nZLint's semantic analyzer does not yet record references to variables on\nmember access expressions (e.g. `bar` on `foo.bar`). It also does not\nhandle method calls correctly. Until these features are added, only\ntop-level `const` variable declarations are checked.\n\n:::\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\n// `std` used, but `Allocator` is not.\nconst std = @import(\"std\");\nconst Allocator = std.mem.Allocator;\n\n// Variables available to other code, either via `export` or `pub`, are not\n// reported.\npub const x = 1;\nexport fn foo(x: u32) void {}\n\n// `extern` functions are not reported\nextern fn bar(a: i32) void;\n```\n\nExamples of **correct** code for this rule:\n```zig\n// Discarded variables are considered \"used\".\nconst x = 1;\n_ = x;\n\n// non-container scoped variables are allowed by this rule but banned by the\n// compiler. `x`, `y`, and `z` are ignored by this rule.\npub fn foo(x: u32) void {\n const y = true;\n var z: u32 = 1;\n}\n```", "default": "warn", "markdownDescription": "## What This Rule Does\n\nDisallows container-scoped variables that are declared but never used. Note\nthat top-level declarations are included.\n\nThe Zig compiler checks for unused parameters, payloads bound by `if`,\n`catch`, etc, and `const`/`var` declaration within functions. However,\nvariables and functions declared in container scopes are not given the same\ntreatment. This rule handles those cases.\n\n:::warning\n\nZLint's semantic analyzer does not yet record references to variables on\nmember access expressions (e.g. `bar` on `foo.bar`). It also does not\nhandle method calls correctly. Until these features are added, only\ntop-level `const` variable declarations are checked.\n\n:::\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\n// `std` used, but `Allocator` is not.\nconst std = @import(\"std\");\nconst Allocator = std.mem.Allocator;\n\n// Variables available to other code, either via `export` or `pub`, are not\n// reported.\npub const x = 1;\nexport fn foo(x: u32) void {}\n\n// `extern` functions are not reported\nextern fn bar(a: i32) void;\n```\n\nExamples of **correct** code for this rule:\n```zig\n// Discarded variables are considered \"used\".\nconst x = 1;\n_ = x;\n\n// non-container scoped variables are allowed by this rule but banned by the\n// compiler. `x`, `y`, and `z` are ignored by this rule.\npub fn foo(x: u32) void {\n const y = true;\n var z: u32 = 1;\n}\n```", "oneOf": [ { "$ref": "#/definitions/Error.Severity" }, { "type": "array", "items": false, "prefixItems": [ { "$ref": "#/definitions/Error.Severity" }, { "$ref": "#/definitions/linter.rules.unused_decls" } ] } ] }, "homeless-try": { "description": "## What This Rule Does\nChecks for `try` statements used outside of error-returning functions.\n\nAs a `compiler`-level lint, this rule checks for errors also caught by the\nZig compiler.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nconst std = @import(\"std\");\n\nvar not_in_a_function = try std.heap.page_allocator.alloc(u8, 8);\n\nfn foo() void {\n var my_str = try std.heap.page_allocator.alloc(u8, 8);\n}\n\nfn bar() !void {\n const Baz = struct {\n property: u32 = try std.heap.page_allocator.alloc(u8, 8),\n };\n}\n```\n\nExamples of **correct** code for this rule:\n```zig\nfn foo() !void {\n var my_str = try std.heap.page_allocator.alloc(u8, 8);\n}\n```\n\nZig allows `try` in comptime scopes in or nested within functions. This rule\ndoes not flag these cases.\n```zig\nconst std = @import(\"std\");\nfn foo(x: u32) void {\n comptime {\n // valid\n try bar(x);\n }\n}\nfn bar(x: u32) !void {\n return if (x == 0) error.Unreachable else void;\n}\n```\n\nZig also allows `try` on functions whose error union sets are empty. ZLint\ndoes _not_ respect this case. Please refactor such functions to not return\nan error union.\n```zig\nconst std = @import(\"std\");\nfn foo() !u32 {\n // compiles, but treated as a violation. `bar` should return `u32`.\n const x = try bar();\n return x + 1;\n}\nfn bar() u32 {\n return 1;\n}\n```", "default": "error", "markdownDescription": "## What This Rule Does\nChecks for `try` statements used outside of error-returning functions.\n\nAs a `compiler`-level lint, this rule checks for errors also caught by the\nZig compiler.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nconst std = @import(\"std\");\n\nvar not_in_a_function = try std.heap.page_allocator.alloc(u8, 8);\n\nfn foo() void {\n var my_str = try std.heap.page_allocator.alloc(u8, 8);\n}\n\nfn bar() !void {\n const Baz = struct {\n property: u32 = try std.heap.page_allocator.alloc(u8, 8),\n };\n}\n```\n\nExamples of **correct** code for this rule:\n```zig\nfn foo() !void {\n var my_str = try std.heap.page_allocator.alloc(u8, 8);\n}\n```\n\nZig allows `try` in comptime scopes in or nested within functions. This rule\ndoes not flag these cases.\n```zig\nconst std = @import(\"std\");\nfn foo(x: u32) void {\n comptime {\n // valid\n try bar(x);\n }\n}\nfn bar(x: u32) !void {\n return if (x == 0) error.Unreachable else void;\n}\n```\n\nZig also allows `try` on functions whose error union sets are empty. ZLint\ndoes _not_ respect this case. Please refactor such functions to not return\nan error union.\n```zig\nconst std = @import(\"std\");\nfn foo() !u32 {\n // compiles, but treated as a violation. `bar` should return `u32`.\n const x = try bar();\n return x + 1;\n}\nfn bar() u32 {\n return 1;\n}\n```", "oneOf": [ { "$ref": "#/definitions/Error.Severity" }, { "type": "array", "items": false, "prefixItems": [ { "$ref": "#/definitions/Error.Severity" }, { "$ref": "#/definitions/linter.rules.homeless_try" } ] } ] }, "no-catch-return": { "description": "## What This Rule Does\nDisallows `catch` blocks that immediately return the caught error.\n\nCatch blocks that do nothing but return their error can and should be\nreplaced with a `try` statement. This rule allows for `catch`es that\nhave side effects such as printing the error or switching over it.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nfn foo() !void {\n riskyOp() catch |e| return e;\n riskyOp() catch |e| { return e; };\n}\n```\n\nExamples of **correct** code for this rule:\n```zig\nconst std = @import(\"std\");\n\nfn foo() !void{\n try riskyOp();\n}\n\n// re-throwing with side effects is fine\nfn bar() !void {\n riskyOp() catch |e| {\n std.debug.print(\"Error: {any}\\n\", .{e});\n return e;\n };\n}\n\n// throwing a new error is fine\nfn baz() !void {\n riskyOp() catch |e| return error.OutOfMemory;\n}\n```", "default": "warn", "markdownDescription": "## What This Rule Does\nDisallows `catch` blocks that immediately return the caught error.\n\nCatch blocks that do nothing but return their error can and should be\nreplaced with a `try` statement. This rule allows for `catch`es that\nhave side effects such as printing the error or switching over it.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nfn foo() !void {\n riskyOp() catch |e| return e;\n riskyOp() catch |e| { return e; };\n}\n```\n\nExamples of **correct** code for this rule:\n```zig\nconst std = @import(\"std\");\n\nfn foo() !void{\n try riskyOp();\n}\n\n// re-throwing with side effects is fine\nfn bar() !void {\n riskyOp() catch |e| {\n std.debug.print(\"Error: {any}\\n\", .{e});\n return e;\n };\n}\n\n// throwing a new error is fine\nfn baz() !void {\n riskyOp() catch |e| return error.OutOfMemory;\n}\n```", "oneOf": [ { "$ref": "#/definitions/Error.Severity" }, { "type": "array", "items": false, "prefixItems": [ { "$ref": "#/definitions/Error.Severity" }, { "$ref": "#/definitions/linter.rules.no_catch_return" } ] } ] }, "empty-file": { "description": "## What This Rule Does\nThis rule checks for empty .zig files in the project.\nA file should be deemed empty if it has no content (zero bytes) or only comments and whitespace characters,\nas defined by the standard library in [`std.ascii.whitespace`](https://ziglang.org/documentation/master/std/#std.ascii.whitespace).\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n\n```zig\n// an \"empty\" file is actually a file without meaningful code: just comments (doc or normal) or whitespace\n```\n\nExamples of **correct** code for this rule:\n```zig\nfn exampleFunction() void {\n}\n```", "default": "warn", "markdownDescription": "## What This Rule Does\nThis rule checks for empty .zig files in the project.\nA file should be deemed empty if it has no content (zero bytes) or only comments and whitespace characters,\nas defined by the standard library in [`std.ascii.whitespace`](https://ziglang.org/documentation/master/std/#std.ascii.whitespace).\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n\n```zig\n// an \"empty\" file is actually a file without meaningful code: just comments (doc or normal) or whitespace\n```\n\nExamples of **correct** code for this rule:\n```zig\nfn exampleFunction() void {\n}\n```", "oneOf": [ { "$ref": "#/definitions/Error.Severity" }, { "type": "array", "items": false, "prefixItems": [ { "$ref": "#/definitions/Error.Severity" }, { "$ref": "#/definitions/linter.rules.empty_file" } ] } ] }, "line-length": { "description": "## What This Rule Does\n\nChecks if any line goes beyond a given number of columns.\n\n## Examples\n\nExamples of **incorrect** code for this rule (with a threshold of 120 columns):\n```zig\nconst std = @import(\"std\");\nconst longStructDeclarationInOneLine = struct { max_length: u32 = 120, a: usize = 123, b: usize = 12354, c: usize = 1234352 };\nfn reallyExtraVerboseFunctionNameToThePointOfBeingACodeSmellAndProbablyAHintThatYouCanGetAwayWithAnotherNameOrSplittingThisIntoSeveralFunctions() u32 {\n return 123;\n}\n```\n\nExamples of **correct** code for this rule (with a threshold of 120 columns):\n```zig\nconst std = @import(\"std\");\nconst longStructInMultipleLines = struct {\n max_length: u32 = 120,\n a: usize = 123,\n b: usize = 12354,\n c: usize = 1234352,\n};\nfn Get123Constant() u32 {\n return 123;\n}\n```", "default": "off", "markdownDescription": "## What This Rule Does\n\nChecks if any line goes beyond a given number of columns.\n\n## Examples\n\nExamples of **incorrect** code for this rule (with a threshold of 120 columns):\n```zig\nconst std = @import(\"std\");\nconst longStructDeclarationInOneLine = struct { max_length: u32 = 120, a: usize = 123, b: usize = 12354, c: usize = 1234352 };\nfn reallyExtraVerboseFunctionNameToThePointOfBeingACodeSmellAndProbablyAHintThatYouCanGetAwayWithAnotherNameOrSplittingThisIntoSeveralFunctions() u32 {\n return 123;\n}\n```\n\nExamples of **correct** code for this rule (with a threshold of 120 columns):\n```zig\nconst std = @import(\"std\");\nconst longStructInMultipleLines = struct {\n max_length: u32 = 120,\n a: usize = 123,\n b: usize = 12354,\n c: usize = 1234352,\n};\nfn Get123Constant() u32 {\n return 123;\n}\n```", "oneOf": [ { "$ref": "#/definitions/Error.Severity" }, { "type": "array", "items": false, "prefixItems": [ { "$ref": "#/definitions/Error.Severity" }, { "$ref": "#/definitions/linter.rules.line_length" } ] } ] }, "no-print": { "description": "## What This Rule Does\nDisallows the use of `std.debug.print`.\n\n`print` statements are great for debugging, but they should be removed\nbefore code gets merged. When you need debug logs in production, use\n`std.log` instead.\n\nThis rule makes a best-effort attempt to ensure `print` calls are actually\nfrom `std.debug.print`. It will not report calls to custom print functions\nif they are defined within the same file. If you are getting false positives\nbecause you import a custom print function, consider disabling this rule on\na file-by-file basis instead of turning it off globally.\n\n### Tests\nBy default, this rule ignores `print`s in test blocks and files. Files are\nconsidered to be a test file if they end with `test.zig`. You may disable\nthis by setting `allow_tests` to `false` in the rule's metadata.\n\n```json\n{\n \"rules\": {\n \"no-print\": [\"warn\", { \"allow_tests\": false }]\n }\n}\n```\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nconst std = @import(\"std\");\nconst debug = std.debug;\nconst print = std.debug.print;\nfn main() void {\n std.debug.print(\"This should not be here: {d}\\n\", .{42});\n debug.print(\"This should not be here: {d}\\n\", .{42});\n print(\"This should not be here: {d}\\n\", .{42});\n}\n```\n\nExamples of **correct** code for this rule:\n```zig\nconst std = @import(\"std\");\nfn foo() u32 {\n std.log.debug(\"running foo\", .{});\n return 1;\n}\n\ntest foo {\n std.debug.print(\"testing foo\\n\", .{});\n try std.testing.expectEqual(1, foo());\n}\n```\n\n```zig\nfn print(comptime msg: []const u8, args: anytype) void {\n // ...\n}\nfn main() void {\n print(\"Staring program\", .{});\n}\n```", "default": "warn", "markdownDescription": "## What This Rule Does\nDisallows the use of `std.debug.print`.\n\n`print` statements are great for debugging, but they should be removed\nbefore code gets merged. When you need debug logs in production, use\n`std.log` instead.\n\nThis rule makes a best-effort attempt to ensure `print` calls are actually\nfrom `std.debug.print`. It will not report calls to custom print functions\nif they are defined within the same file. If you are getting false positives\nbecause you import a custom print function, consider disabling this rule on\na file-by-file basis instead of turning it off globally.\n\n### Tests\nBy default, this rule ignores `print`s in test blocks and files. Files are\nconsidered to be a test file if they end with `test.zig`. You may disable\nthis by setting `allow_tests` to `false` in the rule's metadata.\n\n```json\n{\n \"rules\": {\n \"no-print\": [\"warn\", { \"allow_tests\": false }]\n }\n}\n```\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nconst std = @import(\"std\");\nconst debug = std.debug;\nconst print = std.debug.print;\nfn main() void {\n std.debug.print(\"This should not be here: {d}\\n\", .{42});\n debug.print(\"This should not be here: {d}\\n\", .{42});\n print(\"This should not be here: {d}\\n\", .{42});\n}\n```\n\nExamples of **correct** code for this rule:\n```zig\nconst std = @import(\"std\");\nfn foo() u32 {\n std.log.debug(\"running foo\", .{});\n return 1;\n}\n\ntest foo {\n std.debug.print(\"testing foo\\n\", .{});\n try std.testing.expectEqual(1, foo());\n}\n```\n\n```zig\nfn print(comptime msg: []const u8, args: anytype) void {\n // ...\n}\nfn main() void {\n print(\"Staring program\", .{});\n}\n```", "oneOf": [ { "$ref": "#/definitions/Error.Severity" }, { "type": "array", "items": false, "prefixItems": [ { "$ref": "#/definitions/Error.Severity" }, { "$ref": "#/definitions/linter.rules.no_print" } ] } ] }, "returned-stack-reference": { "description": "## What This Rule Does\nChecks for functions that return references to stack-allocated memory.\n\n:::warning\n\nThis rule is still in early development. PRs to improve it are welcome.\n\n:::\n\nIt is illegal to use stack-allocated memory outside of the function that\nallocated it. Once that function returns and the stack is popped, the memory\nis no longer valid and may cause segfaults or undefined behavior.\n\n```zig\nconst std = @import(\"std\");\nfn foo() *u32 {\n var x: u32 = 1; // x is on the stack\n return &x;\n}\nfn bar() void {\n const x = foo();\n std.debug.print(\"{d}\\n\", .{x}); // crashes\n}\n```\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nconst std = @import(\"std\");\nfn foo() *u32 {\n var x: u32 = 1;\n return &x;\n}\nfn bar() []u32 {\n var x: [1]u32 = .{1};\n return x[0..];\n}\n```\n\nExamples of **correct** code for this rule:\n```zig\nfn foo() *u32 {\n var x = std.heap.page_allocator.create(u32);\n x.* = 1;\n return x;\n}\n```", "default": "off", "markdownDescription": "## What This Rule Does\nChecks for functions that return references to stack-allocated memory.\n\n:::warning\n\nThis rule is still in early development. PRs to improve it are welcome.\n\n:::\n\nIt is illegal to use stack-allocated memory outside of the function that\nallocated it. Once that function returns and the stack is popped, the memory\nis no longer valid and may cause segfaults or undefined behavior.\n\n```zig\nconst std = @import(\"std\");\nfn foo() *u32 {\n var x: u32 = 1; // x is on the stack\n return &x;\n}\nfn bar() void {\n const x = foo();\n std.debug.print(\"{d}\\n\", .{x}); // crashes\n}\n```\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nconst std = @import(\"std\");\nfn foo() *u32 {\n var x: u32 = 1;\n return &x;\n}\nfn bar() []u32 {\n var x: [1]u32 = .{1};\n return x[0..];\n}\n```\n\nExamples of **correct** code for this rule:\n```zig\nfn foo() *u32 {\n var x = std.heap.page_allocator.create(u32);\n x.* = 1;\n return x;\n}\n```", "oneOf": [ { "$ref": "#/definitions/Error.Severity" }, { "type": "array", "items": false, "prefixItems": [ { "$ref": "#/definitions/Error.Severity" }, { "$ref": "#/definitions/linter.rules.returned_stack_reference" } ] } ] }, "duplicate-case": { "description": "## What This Rule Does\nChecks for duplicate cases in switch statements.\n\nThis rule identifies when switch statements have case branches that could\nbe merged together without affecting program behavior. It does _not_ check\nthat the value being switched over is the same; rather it checks whether\nthe target expressions are duplicates.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nfn foo() void {\n const x = switch (1) {\n 1 => 1,\n else => 1,\n };\n}\n\nfn bar(y: u32) void {\n const x = switch (y) {\n 1 => y + 1,\n 2 => 1 + y,\n else => y * 2,\n };\n}\n```\n\nExamples of **correct** code for this rule:\n```zig\nfn foo() void {\n const x = switch (1) {\n 1 => 1,\n 2 => 2,\n };\n}\n\nfn bar(y: u32) void {\n const x = switch (y) {\n 1 => y + 1,\n 2 => y * 2,\n 3 => y - 1,\n };\n}\n```", "default": "off", "markdownDescription": "## What This Rule Does\nChecks for duplicate cases in switch statements.\n\nThis rule identifies when switch statements have case branches that could\nbe merged together without affecting program behavior. It does _not_ check\nthat the value being switched over is the same; rather it checks whether\nthe target expressions are duplicates.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nfn foo() void {\n const x = switch (1) {\n 1 => 1,\n else => 1,\n };\n}\n\nfn bar(y: u32) void {\n const x = switch (y) {\n 1 => y + 1,\n 2 => 1 + y,\n else => y * 2,\n };\n}\n```\n\nExamples of **correct** code for this rule:\n```zig\nfn foo() void {\n const x = switch (1) {\n 1 => 1,\n 2 => 2,\n };\n}\n\nfn bar(y: u32) void {\n const x = switch (y) {\n 1 => y + 1,\n 2 => y * 2,\n 3 => y - 1,\n };\n}\n```", "oneOf": [ { "$ref": "#/definitions/Error.Severity" }, { "type": "array", "items": false, "prefixItems": [ { "$ref": "#/definitions/Error.Severity" }, { "$ref": "#/definitions/linter.rules.duplicate_case" } ] } ] }, "no-unresolved": { "description": "## What This Rule Does\n\nChecks for imports to files that do not exist.\n\nThis rule only checks for file-based imports. Modules added by `build.zig`\nare not checked. More precisely, imports to paths ending in `.zig` will be\nresolved. This rule checks that a file exists at the imported path and is\nnot a directory. Symlinks are allowed but are not followed.\n\n## Examples\nAssume the following directory structure:\n```plaintext\n.\n├── foo.zig\n├── mod\n│ └── bar.zig\n├── not_a_file.zig\n│ └── baz.zig\n└── root.zig\n```\n\nExamples of **incorrect** code for this rule:\n```zig\n// root.zig\nconst x = @import(\"mod/foo.zig\"); // foo.zig is in the root directory.\nconst y = @import(\"not_a_file.zig\"); // directory, not a file\n```\n\nExamples of **correct** code for this rule:\n```zig\n// root.zig\nconst x = @import(\"foo.zig\");\nconst y = @import(\"mod/bar.zig\");\n```", "default": "error", "markdownDescription": "## What This Rule Does\n\nChecks for imports to files that do not exist.\n\nThis rule only checks for file-based imports. Modules added by `build.zig`\nare not checked. More precisely, imports to paths ending in `.zig` will be\nresolved. This rule checks that a file exists at the imported path and is\nnot a directory. Symlinks are allowed but are not followed.\n\n## Examples\nAssume the following directory structure:\n```plaintext\n.\n├── foo.zig\n├── mod\n│ └── bar.zig\n├── not_a_file.zig\n│ └── baz.zig\n└── root.zig\n```\n\nExamples of **incorrect** code for this rule:\n```zig\n// root.zig\nconst x = @import(\"mod/foo.zig\"); // foo.zig is in the root directory.\nconst y = @import(\"not_a_file.zig\"); // directory, not a file\n```\n\nExamples of **correct** code for this rule:\n```zig\n// root.zig\nconst x = @import(\"foo.zig\");\nconst y = @import(\"mod/bar.zig\");\n```", "oneOf": [ { "$ref": "#/definitions/Error.Severity" }, { "type": "array", "items": false, "prefixItems": [ { "$ref": "#/definitions/Error.Severity" }, { "$ref": "#/definitions/linter.rules.no_unresolved" } ] } ] }, "unsafe-undefined": { "description": "## What This Rule Does\nDisallows initializing or assigning variables to `undefined`.\n\nReading uninitialized memory is one of the most common sources of undefined\nbehavior. While debug builds come with runtime safety checks for `undefined`\naccess, they are otherwise undetectable and will not cause panics in release\nbuilds.\n\n### Allowed Scenarios\n\nThere are some cases where using `undefined` makes sense, such as array\ninitialization. Some cases are implicitly allowed, but others should be\ncommunicated to other programmers via a safety comment. Adding `SAFETY:\n[reason]` before the line using `undefined` will not trigger a rule\nviolation.\n\n```zig\n// SAFETY: foo is written to by `initializeFoo`, so `undefined` is never\n// read.\nvar foo: u32 = undefined\ninitializeFoo(&foo);\n\n// SAFETY: this covers the entire initialization\nconst bar: Bar = .{\n .a = undefined,\n .b = undefined,\n};\n```\n\n:::info\nObviously unsafe usages of `undefined`, such `x == undefined`, are not\nallowed even in these exceptions.\n:::\n\n#### Arrays\nArray-typed variable declarations may be initialized to undefined.\nArray-typed container fields with `undefined` as a default value will still\ntrigger a violation.\n\n```zig\n// arrays may be set to undefined without a safety comment\nvar arr: [10]u8 = undefined;\n@memset(&arr, 0);\n\n// This is not allowed\nconst Foo = struct {\n foo: [4]u32 = undefined\n};\n```\n\n#### Whitelisting Types\nYou may whitelist specific types that are allowed to be initialized to `undefined`.\nAny variable with this type will not have a violation triggered, as long as\nthe type is obvious to ZLint's semantic analyzer. By default the whitelist\ncontains `ThreadPool`/`Thread.Pool` from `std.Thread.Pool`\n\n```zig\n// \"unsafe-undefined\": [\"error\", { \"allow_types\": [\"CustomBuffer\"] }]\nconst CustomBuffer = [4096]u8;\nvar buf: CustomBuffer = undefined; // ok\n```\n\n:::warning\nZLint does not have a type checker yet, so implicit struct initializations\nwill not be ignored.\n:::\n\n#### Destructors\nInvalidating freed pointers/data by setting it to `undefined` is helpful for\nfinding use-after-free bugs. Using `undefined` in destructors will not trigger\na violation, unless it is obviously unsafe (e.g. in a comparison).\n\n```zig\nconst std = @import(\"std\");\nconst Foo = struct {\n data: []u8,\n pub fn init(allocator: std.mem.Allocator) !Foo {\n const data = try allocator.alloc(u8, 8);\n return .{ .data = data };\n }\n pub fn deinit(self: *Foo, allocator: std.mem.Allocator) void {\n allocator.free(self.data);\n self.* = undefined; // safe\n }\n};\n```\n\nA method is considered a destructor if it is named\n- `deinit`\n- `destroy`\n- `reset`\n\n#### `test` blocks\nAll usages of `undefined` in `test` blocks are allowed. Code that isn't safe\nwill be caught by the test runner.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nconst x = undefined;\n\n// Consumers of `Foo` should be forced to initialize `x`.\nconst Foo = struct {\n x: *u32 = undefined,\n};\n\nvar y: *u32 = allocator.create(u32);\ny.* = undefined;\n```\n\nExamples of **correct** code for this rule:\n```zig\nconst Foo = struct {\n x: *u32,\n\n fn init(allocator: *std.mem.Allocator, value: u32) void {\n self.x = allocator.create(u32);\n self.x.* = value;\n }\n\n // variables may be re-assigned to `undefined` in destructors\n fn deinit(self: *Foo, alloc: std.mem.Allocator) void {\n alloc.destroy(self.x);\n self.x = undefined;\n }\n};\n\ntest Foo {\n // Allowed. If this is truly unsafe, it will be caught by the test.\n var foo: Foo = undefined;\n // ...\n}\n```", "default": "warn", "markdownDescription": "## What This Rule Does\nDisallows initializing or assigning variables to `undefined`.\n\nReading uninitialized memory is one of the most common sources of undefined\nbehavior. While debug builds come with runtime safety checks for `undefined`\naccess, they are otherwise undetectable and will not cause panics in release\nbuilds.\n\n### Allowed Scenarios\n\nThere are some cases where using `undefined` makes sense, such as array\ninitialization. Some cases are implicitly allowed, but others should be\ncommunicated to other programmers via a safety comment. Adding `SAFETY:\n[reason]` before the line using `undefined` will not trigger a rule\nviolation.\n\n```zig\n// SAFETY: foo is written to by `initializeFoo`, so `undefined` is never\n// read.\nvar foo: u32 = undefined\ninitializeFoo(&foo);\n\n// SAFETY: this covers the entire initialization\nconst bar: Bar = .{\n .a = undefined,\n .b = undefined,\n};\n```\n\n:::info\nObviously unsafe usages of `undefined`, such `x == undefined`, are not\nallowed even in these exceptions.\n:::\n\n#### Arrays\nArray-typed variable declarations may be initialized to undefined.\nArray-typed container fields with `undefined` as a default value will still\ntrigger a violation.\n\n```zig\n// arrays may be set to undefined without a safety comment\nvar arr: [10]u8 = undefined;\n@memset(&arr, 0);\n\n// This is not allowed\nconst Foo = struct {\n foo: [4]u32 = undefined\n};\n```\n\n#### Whitelisting Types\nYou may whitelist specific types that are allowed to be initialized to `undefined`.\nAny variable with this type will not have a violation triggered, as long as\nthe type is obvious to ZLint's semantic analyzer. By default the whitelist\ncontains `ThreadPool`/`Thread.Pool` from `std.Thread.Pool`\n\n```zig\n// \"unsafe-undefined\": [\"error\", { \"allow_types\": [\"CustomBuffer\"] }]\nconst CustomBuffer = [4096]u8;\nvar buf: CustomBuffer = undefined; // ok\n```\n\n:::warning\nZLint does not have a type checker yet, so implicit struct initializations\nwill not be ignored.\n:::\n\n#### Destructors\nInvalidating freed pointers/data by setting it to `undefined` is helpful for\nfinding use-after-free bugs. Using `undefined` in destructors will not trigger\na violation, unless it is obviously unsafe (e.g. in a comparison).\n\n```zig\nconst std = @import(\"std\");\nconst Foo = struct {\n data: []u8,\n pub fn init(allocator: std.mem.Allocator) !Foo {\n const data = try allocator.alloc(u8, 8);\n return .{ .data = data };\n }\n pub fn deinit(self: *Foo, allocator: std.mem.Allocator) void {\n allocator.free(self.data);\n self.* = undefined; // safe\n }\n};\n```\n\nA method is considered a destructor if it is named\n- `deinit`\n- `destroy`\n- `reset`\n\n#### `test` blocks\nAll usages of `undefined` in `test` blocks are allowed. Code that isn't safe\nwill be caught by the test runner.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nconst x = undefined;\n\n// Consumers of `Foo` should be forced to initialize `x`.\nconst Foo = struct {\n x: *u32 = undefined,\n};\n\nvar y: *u32 = allocator.create(u32);\ny.* = undefined;\n```\n\nExamples of **correct** code for this rule:\n```zig\nconst Foo = struct {\n x: *u32,\n\n fn init(allocator: *std.mem.Allocator, value: u32) void {\n self.x = allocator.create(u32);\n self.x.* = value;\n }\n\n // variables may be re-assigned to `undefined` in destructors\n fn deinit(self: *Foo, alloc: std.mem.Allocator) void {\n alloc.destroy(self.x);\n self.x = undefined;\n }\n};\n\ntest Foo {\n // Allowed. If this is truly unsafe, it will be caught by the test.\n var foo: Foo = undefined;\n // ...\n}\n```", "oneOf": [ { "$ref": "#/definitions/Error.Severity" }, { "type": "array", "items": false, "prefixItems": [ { "$ref": "#/definitions/Error.Severity" }, { "$ref": "#/definitions/linter.rules.unsafe_undefined" } ] } ] }, "suppressed-errors": { "description": "## What This Rule Does\nDisallows suppressing or otherwise mishandling caught errors.\n\nFunctions that return error unions could error during \"normal\" execution.\nIf they didn't, they would not return an error or would panic instead.\n\nThis rule enforces that errors are\n1. Propagated up to callers either implicitly or by returning a new error,\n ```zig\n const a = try foo();\n const b = foo catch |e| {\n switch (e) {\n FooError.OutOfMemory => error.OutOfMemory,\n // ...\n }\n }\n ```\n2. Are inspected and handled to continue normal execution\n ```zig\n /// It's fine if users are missing a config file, and open() + err\n // handling is faster than stat() then open()\n var config?: Config = openConfig() catch null;\n ```\n3. Caught and `panic`ed on to provide better crash diagnostics\n ```zig\n const str = try allocator.alloc(u8, size) catch @panic(\"Out of memory\");\n ```\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nconst x = foo() catch {};\nconst y = foo() catch {\n // comments within empty catch blocks are still considered violations.\n};\n// `unreachable` is for code that will never be reached due to invariants.\nconst y = foo() catch unreachable\n```\n\nExamples of **correct** code for this rule:\n```zig\nconst x = foo() catch @panic(\"foo failed.\");\nconst y = foo() catch {\n std.debug.print(\"Foo failed.\\n\", .{});\n};\nconst z = foo() catch null;\n// Writer errors may be safely ignored\nwriter.print(\"{}\", .{5}) catch {};\n\n// suppression is allowed in tests\ntest foo {\n foo() catch {};\n}\n```", "default": "warn", "markdownDescription": "## What This Rule Does\nDisallows suppressing or otherwise mishandling caught errors.\n\nFunctions that return error unions could error during \"normal\" execution.\nIf they didn't, they would not return an error or would panic instead.\n\nThis rule enforces that errors are\n1. Propagated up to callers either implicitly or by returning a new error,\n ```zig\n const a = try foo();\n const b = foo catch |e| {\n switch (e) {\n FooError.OutOfMemory => error.OutOfMemory,\n // ...\n }\n }\n ```\n2. Are inspected and handled to continue normal execution\n ```zig\n /// It's fine if users are missing a config file, and open() + err\n // handling is faster than stat() then open()\n var config?: Config = openConfig() catch null;\n ```\n3. Caught and `panic`ed on to provide better crash diagnostics\n ```zig\n const str = try allocator.alloc(u8, size) catch @panic(\"Out of memory\");\n ```\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nconst x = foo() catch {};\nconst y = foo() catch {\n // comments within empty catch blocks are still considered violations.\n};\n// `unreachable` is for code that will never be reached due to invariants.\nconst y = foo() catch unreachable\n```\n\nExamples of **correct** code for this rule:\n```zig\nconst x = foo() catch @panic(\"foo failed.\");\nconst y = foo() catch {\n std.debug.print(\"Foo failed.\\n\", .{});\n};\nconst z = foo() catch null;\n// Writer errors may be safely ignored\nwriter.print(\"{}\", .{5}) catch {};\n\n// suppression is allowed in tests\ntest foo {\n foo() catch {};\n}\n```", "oneOf": [ { "$ref": "#/definitions/Error.Severity" }, { "type": "array", "items": false, "prefixItems": [ { "$ref": "#/definitions/Error.Severity" }, { "$ref": "#/definitions/linter.rules.suppressed_errors" } ] } ] }, "no-return-try": { "description": "## What This Rule Does\n\nDisallows `return`ing a `try` expression.\n\nReturning an error union directly has the same exact semantics as `try`ing\nit and then returning the result.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nconst std = @import(\"std\");\n\nfn foo() !void {\n return error.OutOfMemory;\n}\n\nfn bar() !void {\n return try foo();\n}\n```\n\nExamples of **correct** code for this rule:\n```zig\nconst std = @import(\"std\");\n\nfn foo() !void {\n return error.OutOfMemory;\n}\n\nfn bar() !void {\n errdefer {\n std.debug.print(\"this still gets printed.\\n\", .{});\n }\n\n return foo();\n}\n```", "default": "off", "markdownDescription": "## What This Rule Does\n\nDisallows `return`ing a `try` expression.\n\nReturning an error union directly has the same exact semantics as `try`ing\nit and then returning the result.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nconst std = @import(\"std\");\n\nfn foo() !void {\n return error.OutOfMemory;\n}\n\nfn bar() !void {\n return try foo();\n}\n```\n\nExamples of **correct** code for this rule:\n```zig\nconst std = @import(\"std\");\n\nfn foo() !void {\n return error.OutOfMemory;\n}\n\nfn bar() !void {\n errdefer {\n std.debug.print(\"this still gets printed.\\n\", .{});\n }\n\n return foo();\n}\n```", "oneOf": [ { "$ref": "#/definitions/Error.Severity" }, { "type": "array", "items": false, "prefixItems": [ { "$ref": "#/definitions/Error.Severity" }, { "$ref": "#/definitions/linter.rules.no_return_try" } ] } ] }, "must-return-ref": { "description": "## What This Rule Does\nDisallows returning copies of types that store a `capacity`.\n\nZig does not have move semantics. Returning a value by value copies it.\nReturning a copy of a struct's field that records how much memory it has\nallocated can easily lead to memory leaks.\n\n```zig\nconst std = @import(\"std\");\npub const Foo = struct {\n list: std.ArrayList(u32),\n pub fn getList(self: *Foo) std.ArrayList(u32) {\n return self.list;\n }\n};\n\npub fn main() !void {\n var foo: Foo = .{\n .list = try std.ArrayList(u32).init(std.heap.page_allocator)\n };\n defer foo.list.deinit();\n var list = foo.getList();\n try list.append(1); // leaked!\n}\n```\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nfn foo(self: *Foo) std.ArrayList(u32) {\n return self.list;\n}\n```\n\nExamples of **correct** code for this rule:\n```zig\n// pass by reference\nfn foo(self: *Foo) *std.ArrayList(u32) {\n return &self.list;\n}\n\n// new instances are fine\nfn foo() ArenaAllocator {\n return std.mem.ArenaAllocator.init(std.heap.page_allocator);\n}\n```", "default": "warn", "markdownDescription": "## What This Rule Does\nDisallows returning copies of types that store a `capacity`.\n\nZig does not have move semantics. Returning a value by value copies it.\nReturning a copy of a struct's field that records how much memory it has\nallocated can easily lead to memory leaks.\n\n```zig\nconst std = @import(\"std\");\npub const Foo = struct {\n list: std.ArrayList(u32),\n pub fn getList(self: *Foo) std.ArrayList(u32) {\n return self.list;\n }\n};\n\npub fn main() !void {\n var foo: Foo = .{\n .list = try std.ArrayList(u32).init(std.heap.page_allocator)\n };\n defer foo.list.deinit();\n var list = foo.getList();\n try list.append(1); // leaked!\n}\n```\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n```zig\nfn foo(self: *Foo) std.ArrayList(u32) {\n return self.list;\n}\n```\n\nExamples of **correct** code for this rule:\n```zig\n// pass by reference\nfn foo(self: *Foo) *std.ArrayList(u32) {\n return &self.list;\n}\n\n// new instances are fine\nfn foo() ArenaAllocator {\n return std.mem.ArenaAllocator.init(std.heap.page_allocator);\n}\n```", "oneOf": [ { "$ref": "#/definitions/Error.Severity" }, { "type": "array", "items": false, "prefixItems": [ { "$ref": "#/definitions/Error.Severity" }, { "$ref": "#/definitions/linter.rules.must_return_ref" } ] } ] } } }, "linter.rules.duplicate_case": { "type": "object", "title": "duplicate-case config", "properties": {} }, "linter.rules.homeless_try": { "type": "object", "title": "homeless-try config", "properties": {} }, "linter.rules.no_print": { "type": "object", "title": "no-print config", "properties": { "allow_tests": { "type": "boolean", "default": true } } }, "linter.rules.empty_file": { "type": "object", "title": "empty-file config", "properties": {} }, "linter.rules.case_convention": { "type": "object", "title": "case-convention config", "properties": {} }, "linter.rules.must_return_ref": { "type": "object", "title": "must-return-ref config", "properties": {} }, "linter.rules.no_catch_return": { "type": "object", "title": "no-catch-return config", "properties": {} }, "linter.rules.line_length": { "type": "object", "title": "line-length config", "properties": { "max_length": { "type": "integer", "minimum": 0, "maximum": 4294967295 } } }, "linter.rules.no_return_try": { "type": "object", "title": "no-return-try config", "properties": {} }, "linter.rules.no_unresolved": { "type": "object", "title": "no-unresolved config", "properties": {} }, "linter.rules.returned_stack_reference": { "type": "object", "title": "returned-stack-reference config", "properties": {} }, "linter.rules.unsafe_undefined": { "type": "object", "title": "unsafe-undefined config", "properties": { "allowed_types": { "type": "array", "default": [ "ThreadPool", "Thread.Pool" ], "items": { "type": "string" } }, "allow_arrays": { "type": "boolean", "default": true } } } } }