@theglitchking/gimme-the-lint 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +6 -3
- package/.claude-plugin/plugin.json +6 -3
- package/CHANGELOG.md +61 -0
- package/README.md +46 -6
- package/lib/adapters/ansible.js +99 -0
- package/lib/adapters/index.js +4 -0
- package/lib/adapters/tflint.js +138 -0
- package/lib/installer.js +27 -0
- package/lib/linter-configs.js +131 -0
- package/lib/project-model.js +17 -0
- package/package.json +8 -3
- package/templates/.ansible-lint.template.yml +20 -0
- package/templates/.gitleaks.template.toml +12 -0
- package/templates/.golangci.template.yml +81 -0
- package/templates/.prettierrc.template.json +14 -0
- package/templates/.tflint.template.hcl +32 -0
- package/templates/biome.template.json +90 -0
- package/templates/clippy-cargo-lints.template.toml +21 -0
- package/templates/clippy.template.toml +23 -0
- package/templates/eslint.config.template.js +23 -1
- package/templates/pyproject.template.toml +10 -0
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Official marketplace for gimme-the-lint - polyglot progressive linting with per-app baselines and drift detection",
|
|
9
|
-
"version": "2.
|
|
9
|
+
"version": "2.2.0"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "gimme-the-lint",
|
|
14
|
-
"description": "Polyglot progressive linting for monorepos — adapter-driven baselines, per-app drift detection, and idempotent skips across JavaScript/TypeScript, Python, Go, and
|
|
15
|
-
"version": "2.
|
|
14
|
+
"description": "Polyglot progressive linting for monorepos — adapter-driven baselines, per-app drift detection, and idempotent skips across JavaScript/TypeScript, Python, Go, Rust, Terraform, and Ansible.",
|
|
15
|
+
"version": "2.2.0",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "TheGlitchKing"
|
|
18
18
|
},
|
|
@@ -30,6 +30,9 @@
|
|
|
30
30
|
"ruff",
|
|
31
31
|
"golangci-lint",
|
|
32
32
|
"clippy",
|
|
33
|
+
"tflint",
|
|
34
|
+
"terraform",
|
|
35
|
+
"ansible-lint",
|
|
33
36
|
"claude-code"
|
|
34
37
|
],
|
|
35
38
|
"category": "productivity",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gimme-the-lint",
|
|
3
|
-
"description": "Polyglot progressive linting for monorepos — adapter-driven baselines, per-app drift detection, and idempotent skips across JavaScript/TypeScript, Python, Go, and
|
|
4
|
-
"version": "2.
|
|
3
|
+
"description": "Polyglot progressive linting for monorepos — adapter-driven baselines, per-app drift detection, and idempotent skips across JavaScript/TypeScript, Python, Go, Rust, Terraform, and Ansible.",
|
|
4
|
+
"version": "2.2.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TheGlitchKing",
|
|
7
7
|
"email": "theglitchking@users.noreply.github.com"
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
"eslint",
|
|
20
20
|
"ruff",
|
|
21
21
|
"golangci-lint",
|
|
22
|
-
"clippy"
|
|
22
|
+
"clippy",
|
|
23
|
+
"tflint",
|
|
24
|
+
"terraform",
|
|
25
|
+
"ansible-lint"
|
|
23
26
|
]
|
|
24
27
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,67 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.2.0] - 2026-05-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Ansible support** via a new `ansible-lint` adapter. ansible-lint plugs into
|
|
12
|
+
the progressive-lint engine like every other linter: it runs
|
|
13
|
+
`ansible-lint -f codeclimate`, parses the CodeClimate JSON report, and only
|
|
14
|
+
new violations block. Supports `--fix`.
|
|
15
|
+
- **Manifest-based discovery** for Ansible: a directory containing `ansible.cfg`
|
|
16
|
+
or `galaxy.yml` is auto-discovered and bound to `ansible-lint`. Ansible
|
|
17
|
+
playbooks are plain YAML with no manifest, so detection keys off those
|
|
18
|
+
unambiguous markers rather than a file extension (a YAML scan would match
|
|
19
|
+
nearly every repo). An Ansible repo with neither marker needs an explicit
|
|
20
|
+
`apps` entry in `gimme-the-lint.config.js`.
|
|
21
|
+
- **Ansible best-practice config** — `install` seeds `.ansible-lint` with the
|
|
22
|
+
`moderate` profile (the strictness lever; raise to `safety` / `production`).
|
|
23
|
+
- README now documents every supported codebase with a default-rules summary
|
|
24
|
+
and the strictness lever for each; `.documentation/lint-rules-guide.md` adds
|
|
25
|
+
a full Ansible section.
|
|
26
|
+
|
|
27
|
+
## [2.1.0] - 2026-05-17
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- **Terraform / OpenTofu support** via a new `tflint` linter adapter. tflint
|
|
31
|
+
plugs into the same progressive-lint engine as every other linter: baselines
|
|
32
|
+
are captured into `.gtl/apps/<app>/baseline.json` under a `tflint` section,
|
|
33
|
+
fingerprinted by `file + rule + message`, and only new violations block.
|
|
34
|
+
- Runs `tflint --format=json`, module-scoped (executes inside the module
|
|
35
|
+
directory for compatibility with tflint versions predating `--chdir`).
|
|
36
|
+
- Parses both `issues` and `errors` — broken HCL surfaces as a blocking
|
|
37
|
+
error rather than slipping through as zero issues.
|
|
38
|
+
- Supports `--fix`; tracks config drift on `.tflint.hcl`.
|
|
39
|
+
- **Extension-based app discovery** for Terraform: a directory containing
|
|
40
|
+
`*.tf` / `*.tofu` source is auto-discovered and bound to `tflint`. Terraform
|
|
41
|
+
has no manifest file, so it is discovered by extension rather than by a
|
|
42
|
+
manifest like `package.json` or `go.mod`. In the root-module layout (`.tf`
|
|
43
|
+
at the repo root *and* in `modules/*/`), the root is treated as a workspace
|
|
44
|
+
root — bind `.` explicitly in `gimme-the-lint.config.js` to lint it. See the
|
|
45
|
+
installation guide.
|
|
46
|
+
- **Best-practice config templates** for every supported codebase. `install`
|
|
47
|
+
now seeds each discovered app with a curated, "recommended tier" config for
|
|
48
|
+
its linter — `eslint.config.js` + `.prettierrc.json`, `biome.json`,
|
|
49
|
+
`pyproject.toml` `[tool.ruff]`, `.golangci.yml` (golangci-lint v2 format),
|
|
50
|
+
`clippy.toml` + `Cargo.toml` `[lints.clippy]`, `.tflint.hcl`. Every write is
|
|
51
|
+
create-if-absent — an existing config is never overwritten (`--force`
|
|
52
|
+
replaces). New module `lib/linter-configs.js`.
|
|
53
|
+
- **Security lint rules** across every codebase. gitleaks remains the universal
|
|
54
|
+
secret scanner (passwords, SSL/private keys, tokens — always blocks, never
|
|
55
|
+
baselined) and its template now also flags PEM private keys and hardcoded
|
|
56
|
+
password assignments. Per-linter security layers are enabled in the shipped
|
|
57
|
+
configs: `gosec` (Go), Ruff `S` / flake8-bandit (Python),
|
|
58
|
+
`eslint-plugin-security` + `eslint-plugin-no-secrets` (JS/TS), and Biome's
|
|
59
|
+
`security` rule group.
|
|
60
|
+
- **Prettier support** — a `.prettierrc.json` template is seeded for ESLint
|
|
61
|
+
apps, and the ESLint config now applies `eslint-config-prettier` so the two
|
|
62
|
+
do not fight over formatting. The ESLint template refresh is additive: every
|
|
63
|
+
pre-existing rule (architecture import guards, `no-cycle`, unused-vars) is
|
|
64
|
+
retained.
|
|
65
|
+
- **New documentation** — `.documentation/lint-rules-guide.md` documents every
|
|
66
|
+
supported codebase, its baseline rules, the security layers, and how/where to
|
|
67
|
+
adjust them.
|
|
68
|
+
|
|
8
69
|
## [2.0.0] - 2026-05-17
|
|
9
70
|
|
|
10
71
|
A ground-up rearchitecture: gimme-the-lint is now a language-agnostic
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ stuff at its own pace; meanwhile no new mess gets in.
|
|
|
19
19
|
**v2.0** generalizes that idea to any linter and any language. It is no longer
|
|
20
20
|
"ESLint + Ruff for webapps" — it is a progressive-lint **engine** with a
|
|
21
21
|
pluggable **linter adapter** for each tool, across **JavaScript/TypeScript, Python,
|
|
22
|
-
Go, and
|
|
22
|
+
Go, Rust, Terraform, and Ansible**, in **any monorepo shape**.
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
@@ -38,8 +38,10 @@ is ever flagged. (In v1 this job was outsourced to the third-party
|
|
|
38
38
|
|
|
39
39
|
Each app is bound to the linters its package manifest implies — `package.json`
|
|
40
40
|
→ ESLint, `pyproject.toml` → Ruff, `go.mod` → golangci-lint, `Cargo.toml` →
|
|
41
|
-
Clippy, `biome.json` → Biome.
|
|
42
|
-
|
|
41
|
+
Clippy, `biome.json` → Biome, `ansible.cfg` / `galaxy.yml` → ansible-lint.
|
|
42
|
+
Terraform has no manifest, so a directory of `*.tf` / `*.tofu` files binds to
|
|
43
|
+
tflint by extension. Drift detection runs per app, so a config or linter-version
|
|
44
|
+
change in one app never churns the baselines of another.
|
|
43
45
|
|
|
44
46
|
---
|
|
45
47
|
|
|
@@ -48,10 +50,14 @@ linter-version change in one app never churns the baselines of another.
|
|
|
48
50
|
- **Progressive linting** — only new violations block; existing ones are baselined
|
|
49
51
|
- **In-house diff engine** — line/column-independent fingerprints survive code shifts
|
|
50
52
|
- **Pluggable linter adapters** — the choice of linter is config, not a hardcode
|
|
51
|
-
- **Polyglot** — JavaScript/TypeScript, Python, Go, Rust out of the box
|
|
53
|
+
- **Polyglot** — JavaScript/TypeScript, Python, Go, Rust, Terraform, Ansible out of the box
|
|
52
54
|
- **Per-app model** — auto-discovers every package in a monorepo; no `frontend/`
|
|
53
55
|
+ `backend/` assumption
|
|
54
56
|
- **Per-app drift detection** — app add/remove, config change, linter version, age
|
|
57
|
+
- **Best-practice configs shipped** — `install` seeds each app with a curated,
|
|
58
|
+
security-aware config for its linter (create-if-absent — never clobbers yours)
|
|
59
|
+
- **Security linting built in** — gitleaks for secrets across every codebase,
|
|
60
|
+
plus per-language security rules (gosec, Ruff `S`, eslint-plugin-security)
|
|
55
61
|
- **Idempotent skips** — an app with code but no installed linter is warn-skipped
|
|
56
62
|
(never blocks) — or fails loudly under `--strict`
|
|
57
63
|
- **Offline install** — air-gapped mode for regulated environments
|
|
@@ -68,6 +74,38 @@ linter-version change in one app never churns the baselines of another.
|
|
|
68
74
|
| Python | `ruff` | `pyproject.toml`, `requirements.txt`, `setup.py` |
|
|
69
75
|
| Go | `golangci-lint` | `go.mod` |
|
|
70
76
|
| Rust | `clippy` (`cargo clippy`) | `Cargo.toml` |
|
|
77
|
+
| Terraform / OpenTofu | `tflint` | `*.tf` / `*.tofu` files (no manifest) |
|
|
78
|
+
| Ansible | `ansible-lint` | `ansible.cfg`, `galaxy.yml` |
|
|
79
|
+
|
|
80
|
+
## Shipped lint configs
|
|
81
|
+
|
|
82
|
+
`install` seeds every discovered app with a best-practice ("recommended" tier)
|
|
83
|
+
config for its linter — **created only if absent**, so your own config is never
|
|
84
|
+
overwritten. Each ships a sensible default rule set and a single **lever** to
|
|
85
|
+
dial strictness up or down:
|
|
86
|
+
|
|
87
|
+
| Codebase | Linter | Default rules (recommended tier) | Strictness lever |
|
|
88
|
+
|----------|--------|----------------------------------|------------------|
|
|
89
|
+
| JS/TS | ESLint | `@eslint/js` + React recommended, import-architecture guards, security plugins, Prettier-compatible | rules block in `eslint.config.js` |
|
|
90
|
+
| JS/TS | Biome | recommended set + full `security` group + console/complexity rules | rule levels in `biome.json` |
|
|
91
|
+
| Python | Ruff | pyflakes / pycodestyle / isort / bugbear / pyupgrade + `S` security + comprehensions / simplify | `select` / `ignore` in `pyproject.toml` |
|
|
92
|
+
| Go | golangci-lint | `standard` set + correctness & quality linters + `gosec` | `linters.enable` in `.golangci.yml` |
|
|
93
|
+
| Rust | Clippy | `pedantic` + `cargo` at `warn`, noisy lints allowed back | `[lints.clippy]` levels in `Cargo.toml` |
|
|
94
|
+
| Terraform | tflint | bundled `terraform` ruleset, `recommended` preset | `preset` in `.tflint.hcl` (`recommended` → `all`) |
|
|
95
|
+
| Ansible | ansible-lint | `moderate` profile | `profile` in `.ansible-lint` (`min` → `production`) |
|
|
96
|
+
| Secrets (all) | gitleaks | default ruleset + key / password rules — **always blocks** | `[allowlist]` in `.gitleaks.toml` |
|
|
97
|
+
|
|
98
|
+
Every shipped config carries a **security layer**. gitleaks scans every file in
|
|
99
|
+
every codebase for secrets (passwords, SSL/private keys, tokens) and always
|
|
100
|
+
blocks — secrets are never baselined. Each linter adds language-specific
|
|
101
|
+
security rules on top (`gosec`, Ruff `S` / flake8-bandit, `eslint-plugin-security`,
|
|
102
|
+
Biome's `security` group), which follow normal progressive baselining.
|
|
103
|
+
|
|
104
|
+
To go stricter, pull the lever in the table above — because violations are
|
|
105
|
+
progressively baselined, raising strictness never blocks existing code, only new
|
|
106
|
+
code is held to the higher bar. Full per-codebase detail — every default rule
|
|
107
|
+
and how to adjust it — is in
|
|
108
|
+
[`.documentation/lint-rules-guide.md`](.documentation/lint-rules-guide.md).
|
|
71
109
|
|
|
72
110
|
---
|
|
73
111
|
|
|
@@ -262,7 +300,8 @@ lib/
|
|
|
262
300
|
├── diff-engine.js pure diff: new vs baselined vs fixed
|
|
263
301
|
├── baseline-store.js one baseline.json format for every linter
|
|
264
302
|
├── adapters/ one adapter per linter (eslint, biome, ruff,
|
|
265
|
-
│ golangci-lint, clippy
|
|
303
|
+
│ golangci-lint, clippy, tflint, ansible-lint)
|
|
304
|
+
│ + the base contract
|
|
266
305
|
├── project-model.js discovers apps + binds them to linters
|
|
267
306
|
├── units.js resolves apps → {dir, linters, baseline path}
|
|
268
307
|
├── check.js runCheck: lint → diff → report
|
|
@@ -282,7 +321,8 @@ git hooks, GitHub Action and Claude Code plugin are thin front doors over it.
|
|
|
282
321
|
- **Node.js** >= 20
|
|
283
322
|
- **Git** (for hooks and staged-file detection)
|
|
284
323
|
- A linter for each language you use (`eslint`/`biome`, `ruff`, `golangci-lint`,
|
|
285
|
-
`clippy`) — any language whose linter is absent is
|
|
324
|
+
`clippy`, `tflint`, `ansible-lint`) — any language whose linter is absent is
|
|
325
|
+
simply skipped
|
|
286
326
|
|
|
287
327
|
## License
|
|
288
328
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { LinterAdapter } = require('./adapter');
|
|
4
|
+
const { createViolation, SEVERITY } = require('../violation');
|
|
5
|
+
|
|
6
|
+
// ansible-lint adapter. ansible-lint is the de-facto linter for Ansible
|
|
7
|
+
// playbooks, roles and collections; `-f codeclimate` emits a machine-readable
|
|
8
|
+
// JSON report. Ansible has no manifest file and playbooks are plain YAML, so
|
|
9
|
+
// detection keys off the unambiguous Ansible markers `ansible.cfg` and
|
|
10
|
+
// `galaxy.yml` — a YAML extension scan would match virtually every repo.
|
|
11
|
+
// ansible-lint auto-discovers playbooks/roles from the directory it runs in.
|
|
12
|
+
|
|
13
|
+
const ANSIBLE_CONFIG_FILES = ['.ansible-lint', '.config/ansible-lint.yml'];
|
|
14
|
+
|
|
15
|
+
/** Map an ansible-lint (CodeClimate) severity onto a NormalizedViolation severity. */
|
|
16
|
+
function mapSeverity(severity) {
|
|
17
|
+
switch (String(severity).toLowerCase()) {
|
|
18
|
+
case 'info':
|
|
19
|
+
return SEVERITY.INFO;
|
|
20
|
+
case 'minor':
|
|
21
|
+
return SEVERITY.WARNING;
|
|
22
|
+
case 'major':
|
|
23
|
+
case 'critical':
|
|
24
|
+
case 'blocker':
|
|
25
|
+
return SEVERITY.ERROR;
|
|
26
|
+
default:
|
|
27
|
+
return SEVERITY.WARNING;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class AnsibleLintAdapter extends LinterAdapter {
|
|
32
|
+
get id() {
|
|
33
|
+
return 'ansible-lint';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get languages() {
|
|
37
|
+
return ['ansible'];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get supportsFix() {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get manifestFiles() {
|
|
45
|
+
return ['ansible.cfg', 'galaxy.yml'];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Ansible source is plain YAML — far too broad to scan by extension, so
|
|
49
|
+
// detection is manifest-only (see manifestFiles).
|
|
50
|
+
get sourceExtensions() {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
configFiles() {
|
|
55
|
+
return ANSIBLE_CONFIG_FILES;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
buildCommand(targets, opts = {}) {
|
|
59
|
+
const args = ['-f', 'codeclimate'];
|
|
60
|
+
if (opts.fix && this.supportsFix) args.push('--fix');
|
|
61
|
+
const paths = targets && targets.length ? targets.slice() : ['.'];
|
|
62
|
+
args.push(...paths);
|
|
63
|
+
return { cmd: this.binary, args, cwd: opts.cwd || this.projectRoot };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
parse(stdout) {
|
|
67
|
+
const text = (stdout || '').trim();
|
|
68
|
+
if (!text) return [];
|
|
69
|
+
|
|
70
|
+
let report;
|
|
71
|
+
try {
|
|
72
|
+
report = JSON.parse(text);
|
|
73
|
+
} catch {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
if (!Array.isArray(report)) return [];
|
|
77
|
+
|
|
78
|
+
return report.map((issue) => {
|
|
79
|
+
const loc = issue.location || {};
|
|
80
|
+
const lines = loc.lines || {};
|
|
81
|
+
const begin = (loc.positions && loc.positions.begin) || {};
|
|
82
|
+
// CodeClimate locations come as either { lines: { begin } } or
|
|
83
|
+
// { positions: { begin: { line, column } } }.
|
|
84
|
+
let line = lines.begin != null ? lines.begin : begin.line;
|
|
85
|
+
if (line && typeof line === 'object') line = line.line;
|
|
86
|
+
return createViolation({
|
|
87
|
+
file: this._relativize(loc.path || ''),
|
|
88
|
+
line: Number(line) || 0,
|
|
89
|
+
col: Number(begin.column) || 0,
|
|
90
|
+
ruleId: issue.check_name || 'ansible-lint',
|
|
91
|
+
severity: mapSeverity(issue.severity),
|
|
92
|
+
message: issue.description || '',
|
|
93
|
+
source: 'ansible-lint',
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = AnsibleLintAdapter;
|
package/lib/adapters/index.js
CHANGED
|
@@ -6,6 +6,8 @@ const BiomeAdapter = require('./biome');
|
|
|
6
6
|
const RuffAdapter = require('./ruff');
|
|
7
7
|
const GolangciLintAdapter = require('./golangci-lint');
|
|
8
8
|
const ClippyAdapter = require('./clippy');
|
|
9
|
+
const TflintAdapter = require('./tflint');
|
|
10
|
+
const AnsibleLintAdapter = require('./ansible');
|
|
9
11
|
|
|
10
12
|
// The adapter registry. `tool` strings in gimme-the-lint.config.js resolve to
|
|
11
13
|
// concrete adapters here. Adding a language to gimme-the-lint means adding one
|
|
@@ -17,6 +19,8 @@ const REGISTRY = {
|
|
|
17
19
|
ruff: RuffAdapter,
|
|
18
20
|
'golangci-lint': GolangciLintAdapter,
|
|
19
21
|
clippy: ClippyAdapter,
|
|
22
|
+
tflint: TflintAdapter,
|
|
23
|
+
'ansible-lint': AnsibleLintAdapter,
|
|
20
24
|
};
|
|
21
25
|
|
|
22
26
|
/** Construct an adapter instance by id. Throws on an unknown id. */
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { LinterAdapter } = require('./adapter');
|
|
6
|
+
const { createViolation, SEVERITY } = require('../violation');
|
|
7
|
+
|
|
8
|
+
// tflint adapter. tflint is the de-facto Terraform/OpenTofu linter; it emits a
|
|
9
|
+
// machine-readable report under `--format json`. Terraform has no manifest
|
|
10
|
+
// file — a directory of *.tf (or *.tofu) IS the module — so detection here is
|
|
11
|
+
// extension-based. tflint is module-scoped: it lints the .tf files of one
|
|
12
|
+
// directory, so the adapter resolves and runs inside that directory, mirroring
|
|
13
|
+
// the crate/module handling in the Clippy and golangci-lint adapters.
|
|
14
|
+
|
|
15
|
+
const TFLINT_CONFIG_FILES = ['.tflint.hcl'];
|
|
16
|
+
|
|
17
|
+
/** Map a tflint severity string onto a NormalizedViolation severity. */
|
|
18
|
+
function mapSeverity(severity) {
|
|
19
|
+
switch (String(severity).toLowerCase()) {
|
|
20
|
+
case 'error':
|
|
21
|
+
return SEVERITY.ERROR;
|
|
22
|
+
case 'notice':
|
|
23
|
+
case 'info':
|
|
24
|
+
return SEVERITY.INFO;
|
|
25
|
+
case 'warning':
|
|
26
|
+
default:
|
|
27
|
+
return SEVERITY.WARNING;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class TflintAdapter extends LinterAdapter {
|
|
32
|
+
get id() {
|
|
33
|
+
return 'tflint';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get languages() {
|
|
37
|
+
return ['terraform'];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get supportsFix() {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// tflint config files double as a detection hint; the real signal is the
|
|
45
|
+
// presence of *.tf source (see sourceExtensions / project-model.js).
|
|
46
|
+
get manifestFiles() {
|
|
47
|
+
return ['.tflint.hcl', '.terraform.lock.hcl'];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get sourceExtensions() {
|
|
51
|
+
return ['.tf', '.tofu'];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
configFiles() {
|
|
55
|
+
return TFLINT_CONFIG_FILES;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
buildCommand(targets, opts = {}) {
|
|
59
|
+
const args = ['--format=json'];
|
|
60
|
+
if (opts.fix && this.supportsFix) args.push('--fix');
|
|
61
|
+
|
|
62
|
+
// tflint lints whichever directory it runs in. Resolve a run directory
|
|
63
|
+
// from the target — a directory target directly, or the parent directory
|
|
64
|
+
// of a file target (changed-files mode) — and lint there with no path
|
|
65
|
+
// arguments, which keeps the adapter compatible with tflint versions that
|
|
66
|
+
// predate `--chdir`.
|
|
67
|
+
let cwd = opts.cwd || this.projectRoot;
|
|
68
|
+
const first = targets && targets.length ? targets[0] : null;
|
|
69
|
+
if (first && first !== '.') {
|
|
70
|
+
const abs = path.resolve(this.projectRoot, first);
|
|
71
|
+
try {
|
|
72
|
+
cwd = fs.statSync(abs).isDirectory() ? abs : path.dirname(abs);
|
|
73
|
+
} catch {
|
|
74
|
+
// Unresolvable target — fall back to the default cwd.
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { cmd: this.binary, args, cwd };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
parse(stdout) {
|
|
81
|
+
const text = (stdout || '').trim();
|
|
82
|
+
if (!text) return [];
|
|
83
|
+
|
|
84
|
+
let report;
|
|
85
|
+
try {
|
|
86
|
+
report = JSON.parse(text);
|
|
87
|
+
} catch {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const violations = [];
|
|
92
|
+
|
|
93
|
+
const issues = Array.isArray(report.issues) ? report.issues : [];
|
|
94
|
+
for (const issue of issues) {
|
|
95
|
+
const range = issue.range || {};
|
|
96
|
+
const start = range.start || {};
|
|
97
|
+
const end = range.end || {};
|
|
98
|
+
const rule = issue.rule || {};
|
|
99
|
+
violations.push(
|
|
100
|
+
createViolation({
|
|
101
|
+
file: this._relativize(range.filename || ''),
|
|
102
|
+
line: start.line,
|
|
103
|
+
col: start.column,
|
|
104
|
+
endLine: end.line,
|
|
105
|
+
endCol: end.column,
|
|
106
|
+
ruleId: rule.name || 'tflint',
|
|
107
|
+
severity: mapSeverity(rule.severity),
|
|
108
|
+
message: issue.message,
|
|
109
|
+
source: 'tflint',
|
|
110
|
+
})
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// tflint reports configuration / HCL parse failures in a separate
|
|
115
|
+
// `errors` array — treat each as a blocking error so broken Terraform
|
|
116
|
+
// fails the check rather than slipping through as zero issues.
|
|
117
|
+
const errors = Array.isArray(report.errors) ? report.errors : [];
|
|
118
|
+
for (const err of errors) {
|
|
119
|
+
const range = err.range || {};
|
|
120
|
+
const start = range.start || {};
|
|
121
|
+
violations.push(
|
|
122
|
+
createViolation({
|
|
123
|
+
file: this._relativize(range.filename || ''),
|
|
124
|
+
line: start.line,
|
|
125
|
+
col: start.column,
|
|
126
|
+
ruleId: 'tflint-error',
|
|
127
|
+
severity: SEVERITY.ERROR,
|
|
128
|
+
message: err.message || err.summary || 'tflint error',
|
|
129
|
+
source: 'tflint',
|
|
130
|
+
})
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return violations;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = TflintAdapter;
|
package/lib/installer.js
CHANGED
|
@@ -7,6 +7,7 @@ const venvManager = require('./venv-manager');
|
|
|
7
7
|
const gitHooksManager = require('./git-hooks-manager');
|
|
8
8
|
const toolchain = require('./toolchain');
|
|
9
9
|
const { runBaseline } = require('./baseline');
|
|
10
|
+
const { writeLinterConfigs } = require('./linter-configs');
|
|
10
11
|
|
|
11
12
|
// init() supports two adoption modes beyond the default:
|
|
12
13
|
// options.offline — air-gapped install: no npm/pip fetches; assumes the
|
|
@@ -99,6 +100,32 @@ async function init(projectRoot, options = {}) {
|
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
|
|
103
|
+
// Step 7b: Seed each discovered app with its linters' best-practice config
|
|
104
|
+
// (create-if-absent — a user's existing linter config is never overwritten).
|
|
105
|
+
try {
|
|
106
|
+
results.linterConfigs = await writeLinterConfigs(projectRoot, {
|
|
107
|
+
force: options.force,
|
|
108
|
+
reactVersion: options.reactVersion,
|
|
109
|
+
});
|
|
110
|
+
const seeded = results.linterConfigs.filter(
|
|
111
|
+
(c) => c.status === 'created' || c.status === 'appended'
|
|
112
|
+
);
|
|
113
|
+
if (seeded.length) {
|
|
114
|
+
results.steps.push(
|
|
115
|
+
`Seeded best-practice linter configs: ${seeded
|
|
116
|
+
.map((c) => `${c.app === '.' ? '' : c.app + '/'}${c.file}`)
|
|
117
|
+
.join(', ')}`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
for (const c of results.linterConfigs) {
|
|
121
|
+
if (c.status === 'error') {
|
|
122
|
+
results.errors.push(`Linter config (${c.linter} @ ${c.app}): ${c.reason}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} catch (e) {
|
|
126
|
+
results.errors.push(`Linter configs: ${e.message}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
102
129
|
// Step 8: Setup Python venv (backend) — skipped entirely in offline mode,
|
|
103
130
|
// where the toolchain is provisioned outside gimme-the-lint.
|
|
104
131
|
if (options.offline) {
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const projectModel = require('./project-model');
|
|
6
|
+
const configManager = require('./config-manager');
|
|
7
|
+
|
|
8
|
+
// Ships gimme-the-lint's best-practice ("recommended" tier) linter configs
|
|
9
|
+
// into a project. Every write is CREATE-IF-ABSENT — a user's existing linter
|
|
10
|
+
// config is never overwritten (pass { force: true } to replace).
|
|
11
|
+
//
|
|
12
|
+
// For eslint / golangci-lint / clippy / tflint the config file is not the
|
|
13
|
+
// linter's detection key, so install genuinely seeds a config. For biome and
|
|
14
|
+
// ruff the config file IS the detection key (biome.json / pyproject.toml), so
|
|
15
|
+
// a discovered app already has one — the template then only applies on adoption
|
|
16
|
+
// or under --force.
|
|
17
|
+
|
|
18
|
+
// linter id -> { template filename, destination filename in the app dir }
|
|
19
|
+
const LINTER_CONFIGS = {
|
|
20
|
+
eslint: { template: 'eslint.config.template.js', dest: 'eslint.config.js' },
|
|
21
|
+
biome: { template: 'biome.template.json', dest: 'biome.json' },
|
|
22
|
+
ruff: { template: 'pyproject.template.toml', dest: 'pyproject.toml' },
|
|
23
|
+
'golangci-lint': { template: '.golangci.template.yml', dest: '.golangci.yml' },
|
|
24
|
+
tflint: { template: '.tflint.template.hcl', dest: '.tflint.hcl' },
|
|
25
|
+
clippy: { template: 'clippy.template.toml', dest: 'clippy.toml' },
|
|
26
|
+
'ansible-lint': { template: '.ansible-lint.template.yml', dest: '.ansible-lint' },
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Copy one template into an app dir, create-if-absent. */
|
|
30
|
+
async function writeConfig(appRoot, linterId, options = {}) {
|
|
31
|
+
const spec = LINTER_CONFIGS[linterId];
|
|
32
|
+
if (!spec) return { linter: linterId, status: 'skipped', reason: 'no template' };
|
|
33
|
+
|
|
34
|
+
const dest = path.join(appRoot, spec.dest);
|
|
35
|
+
const result = { linter: linterId, file: spec.dest };
|
|
36
|
+
|
|
37
|
+
if ((await fs.pathExists(dest)) && !options.force) {
|
|
38
|
+
result.status = 'exists';
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const subs =
|
|
43
|
+
linterId === 'eslint' ? { REACT_VERSION: options.reactVersion || '18.3' } : {};
|
|
44
|
+
await configManager.copyTemplate(spec.template, dest, subs);
|
|
45
|
+
result.status = 'created';
|
|
46
|
+
} catch (e) {
|
|
47
|
+
result.status = 'error';
|
|
48
|
+
result.reason = e.message;
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** ESLint apps also get a Prettier config (create-if-absent). */
|
|
54
|
+
async function writePrettier(appRoot, options = {}) {
|
|
55
|
+
const dest = path.join(appRoot, '.prettierrc.json');
|
|
56
|
+
const result = { linter: 'prettier', file: '.prettierrc.json' };
|
|
57
|
+
if ((await fs.pathExists(dest)) && !options.force) {
|
|
58
|
+
result.status = 'exists';
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
await configManager.copyTemplate('.prettierrc.template.json', dest);
|
|
63
|
+
result.status = 'created';
|
|
64
|
+
} catch (e) {
|
|
65
|
+
result.status = 'error';
|
|
66
|
+
result.reason = e.message;
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Clippy lint levels can only be declared in Cargo.toml's [lints.clippy]
|
|
73
|
+
* table (clippy.toml tunes thresholds only). Append the table — but only if
|
|
74
|
+
* Cargo.toml has no [lints] / [workspace.lints] table already.
|
|
75
|
+
*/
|
|
76
|
+
async function writeClippyLints(appRoot) {
|
|
77
|
+
const cargoPath = path.join(appRoot, 'Cargo.toml');
|
|
78
|
+
const result = { linter: 'clippy', file: 'Cargo.toml [lints.clippy]' };
|
|
79
|
+
|
|
80
|
+
if (!(await fs.pathExists(cargoPath))) {
|
|
81
|
+
return { ...result, status: 'skipped', reason: 'no Cargo.toml' };
|
|
82
|
+
}
|
|
83
|
+
const content = await fs.readFile(cargoPath, 'utf8');
|
|
84
|
+
if (/^\s*\[(workspace\.)?lints/m.test(content)) {
|
|
85
|
+
return { ...result, status: 'exists' };
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const snippet = await fs.readFile(
|
|
89
|
+
path.join(configManager.TEMPLATES_DIR, 'clippy-cargo-lints.template.toml'),
|
|
90
|
+
'utf8'
|
|
91
|
+
);
|
|
92
|
+
const sep = content.endsWith('\n') ? '\n' : '\n\n';
|
|
93
|
+
await fs.writeFile(cargoPath, content + sep + snippet, 'utf8');
|
|
94
|
+
return { ...result, status: 'appended' };
|
|
95
|
+
} catch (e) {
|
|
96
|
+
return { ...result, status: 'error', reason: e.message };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Discover every app and seed its linters' best-practice configs.
|
|
102
|
+
* @returns {Promise<{app, linter, file, status, reason?}[]>}
|
|
103
|
+
*/
|
|
104
|
+
async function writeLinterConfigs(projectRoot, options = {}) {
|
|
105
|
+
const root = projectRoot || process.cwd();
|
|
106
|
+
const apps = projectModel.discoverApps(root);
|
|
107
|
+
const written = [];
|
|
108
|
+
|
|
109
|
+
for (const app of apps) {
|
|
110
|
+
const appRoot = path.resolve(root, app.appPath);
|
|
111
|
+
for (const linterId of app.linters) {
|
|
112
|
+
written.push({ app: app.appPath, ...(await writeConfig(appRoot, linterId, options)) });
|
|
113
|
+
|
|
114
|
+
if (linterId === 'eslint') {
|
|
115
|
+
written.push({ app: app.appPath, ...(await writePrettier(appRoot, options)) });
|
|
116
|
+
}
|
|
117
|
+
if (linterId === 'clippy') {
|
|
118
|
+
written.push({ app: app.appPath, ...(await writeClippyLints(appRoot)) });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return written;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = {
|
|
126
|
+
LINTER_CONFIGS,
|
|
127
|
+
writeConfig,
|
|
128
|
+
writePrettier,
|
|
129
|
+
writeClippyLints,
|
|
130
|
+
writeLinterConfigs,
|
|
131
|
+
};
|
package/lib/project-model.js
CHANGED
|
@@ -21,8 +21,24 @@ const MANIFEST_LINTERS = {
|
|
|
21
21
|
'requirements.txt': 'ruff',
|
|
22
22
|
'go.mod': 'golangci-lint',
|
|
23
23
|
'Cargo.toml': 'clippy',
|
|
24
|
+
'ansible.cfg': 'ansible-lint',
|
|
25
|
+
'galaxy.yml': 'ansible-lint',
|
|
24
26
|
};
|
|
25
27
|
|
|
28
|
+
// Terraform / OpenTofu have no manifest file — a directory of *.tf (or *.tofu)
|
|
29
|
+
// IS the module — so they are discovered by source extension rather than by a
|
|
30
|
+
// manifest filename, unlike every other supported language.
|
|
31
|
+
const TERRAFORM_EXTENSIONS = ['.tf', '.tofu'];
|
|
32
|
+
|
|
33
|
+
/** True if any directory entry is a Terraform/OpenTofu source file. */
|
|
34
|
+
function hasTerraformSource(entries) {
|
|
35
|
+
return entries.some(
|
|
36
|
+
(entry) =>
|
|
37
|
+
entry.isFile() &&
|
|
38
|
+
TERRAFORM_EXTENSIONS.some((ext) => entry.name.endsWith(ext))
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
26
42
|
// Template/scaffold directories: linting them is pure noise. Skipped by
|
|
27
43
|
// convention; projects can add more via `skipPatterns` in the config.
|
|
28
44
|
const DEFAULT_SKIP_PATTERNS = ['_template-*', '__template__', '*.template.*'];
|
|
@@ -67,6 +83,7 @@ function findManifestDirs(projectRoot) {
|
|
|
67
83
|
linters.add(MANIFEST_LINTERS[entry.name]);
|
|
68
84
|
}
|
|
69
85
|
}
|
|
86
|
+
if (hasTerraformSource(entries)) linters.add('tflint');
|
|
70
87
|
if (linters.size > 0) {
|
|
71
88
|
found.push({ dir: relDir || '.', linters });
|
|
72
89
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theglitchking/gimme-the-lint",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Polyglot progressive linting for monorepos — adapter-driven baselines, per-app drift detection, and idempotent skips across JavaScript/TypeScript, Python, Go, and
|
|
3
|
+
"version": "2.2.0",
|
|
4
|
+
"description": "Polyglot progressive linting for monorepos — adapter-driven baselines, per-app drift detection, and idempotent skips across JavaScript/TypeScript, Python, Go, Rust, Terraform, and Ansible.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"linting",
|
|
7
7
|
"progressive-linting",
|
|
@@ -20,7 +20,12 @@
|
|
|
20
20
|
"golangci-lint",
|
|
21
21
|
"go",
|
|
22
22
|
"clippy",
|
|
23
|
-
"rust"
|
|
23
|
+
"rust",
|
|
24
|
+
"tflint",
|
|
25
|
+
"terraform",
|
|
26
|
+
"opentofu",
|
|
27
|
+
"ansible-lint",
|
|
28
|
+
"ansible"
|
|
24
29
|
],
|
|
25
30
|
"author": {
|
|
26
31
|
"name": "TheGlitchKing",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Generated by gimme-the-lint — ansible-lint (recommended tier)
|
|
2
|
+
#
|
|
3
|
+
# `profile` is ansible-lint's strictness lever, from least to most strict:
|
|
4
|
+
# min -> basic -> moderate -> safety -> shared -> production
|
|
5
|
+
# "moderate" is the recommended baseline. Raise to "safety" or "production"
|
|
6
|
+
# for stricter enforcement.
|
|
7
|
+
profile: moderate
|
|
8
|
+
|
|
9
|
+
exclude_paths:
|
|
10
|
+
- .cache/
|
|
11
|
+
- .git/
|
|
12
|
+
- molecule/
|
|
13
|
+
|
|
14
|
+
# Downgrade specific rules to non-blocking warnings:
|
|
15
|
+
# warn_list:
|
|
16
|
+
# - experimental
|
|
17
|
+
#
|
|
18
|
+
# Skip specific rules entirely:
|
|
19
|
+
# skip_list:
|
|
20
|
+
# - yaml[line-length]
|
|
@@ -45,6 +45,18 @@ regex = '''(?i)(postgresql|postgres|mysql|mongodb)://[a-zA-Z0-9_-]+:([^@\s]+)@''
|
|
|
45
45
|
tags = ["database", "password"]
|
|
46
46
|
secretGroup = 2
|
|
47
47
|
|
|
48
|
+
[[rules]]
|
|
49
|
+
id = "private-key-pem"
|
|
50
|
+
description = "Private key / SSL cert key (PEM, OpenSSH, PKCS)"
|
|
51
|
+
regex = '''-----BEGIN ([A-Z]+ )?PRIVATE KEY( BLOCK)?-----'''
|
|
52
|
+
tags = ["key", "private", "ssl"]
|
|
53
|
+
|
|
54
|
+
[[rules]]
|
|
55
|
+
id = "hardcoded-password"
|
|
56
|
+
description = "Hardcoded password / secret assignment"
|
|
57
|
+
regex = '''(?i)\b(password|passwd|pwd|secret)\b\s*[:=]\s*['"][^'"\s$]{6,}['"]'''
|
|
58
|
+
tags = ["password", "secret"]
|
|
59
|
+
|
|
48
60
|
# Allowlist (false positive prevention)
|
|
49
61
|
[allowlist]
|
|
50
62
|
description = "Allowlist for false positives"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Generated by gimme-the-lint — golangci-lint v2 (recommended tier)
|
|
2
|
+
# Requires golangci-lint v2.x. Run `golangci-lint migrate` to convert v1 configs.
|
|
3
|
+
version: "2"
|
|
4
|
+
|
|
5
|
+
run:
|
|
6
|
+
timeout: 5m
|
|
7
|
+
tests: true
|
|
8
|
+
|
|
9
|
+
formatters:
|
|
10
|
+
enable:
|
|
11
|
+
- goimports # superset of gofmt; also manages import grouping
|
|
12
|
+
- gofumpt # stricter gofmt
|
|
13
|
+
# settings:
|
|
14
|
+
# goimports:
|
|
15
|
+
# local-prefixes:
|
|
16
|
+
# - github.com/your-org/your-project # set to your module path
|
|
17
|
+
|
|
18
|
+
linters:
|
|
19
|
+
default: standard # errcheck, govet, ineffassign, staticcheck, unused
|
|
20
|
+
|
|
21
|
+
enable:
|
|
22
|
+
# --- Correctness ---
|
|
23
|
+
- copyloopvar # loop-variable capture bugs
|
|
24
|
+
- bodyclose # HTTP response bodies must be closed
|
|
25
|
+
- errorlint # correct error wrapping / unwrapping
|
|
26
|
+
- nilerr # `return nil` when the error is non-nil
|
|
27
|
+
- noctx # HTTP requests must carry a context
|
|
28
|
+
- durationcheck # bad time.Duration arithmetic
|
|
29
|
+
- makezero # make([]T, n) then append — usually a bug
|
|
30
|
+
# --- Security ---
|
|
31
|
+
- gosec # hardcoded creds, injection, weak crypto, insecure TLS
|
|
32
|
+
# --- Code quality ---
|
|
33
|
+
- revive # modern golint replacement
|
|
34
|
+
- gocritic # broad diagnostic / performance checks
|
|
35
|
+
- gocyclo # cyclomatic complexity
|
|
36
|
+
- unconvert # unnecessary type conversions
|
|
37
|
+
- unparam # parameters that always receive the same value
|
|
38
|
+
- nolintlint # malformed or unexplained //nolint directives
|
|
39
|
+
- misspell # misspellings in comments / strings
|
|
40
|
+
- usestdlibvars # use stdlib constants (http.MethodGet, etc.)
|
|
41
|
+
|
|
42
|
+
settings:
|
|
43
|
+
errcheck:
|
|
44
|
+
check-type-assertions: true
|
|
45
|
+
gocyclo:
|
|
46
|
+
min-complexity: 15
|
|
47
|
+
gocritic:
|
|
48
|
+
enabled-tags:
|
|
49
|
+
- diagnostic
|
|
50
|
+
- performance
|
|
51
|
+
disabled-checks:
|
|
52
|
+
- ifElseChain
|
|
53
|
+
gosec:
|
|
54
|
+
# G104 overlaps errcheck; G115 (int-overflow) is high false-positive.
|
|
55
|
+
excludes:
|
|
56
|
+
- G104
|
|
57
|
+
- G115
|
|
58
|
+
severity: medium
|
|
59
|
+
confidence: medium
|
|
60
|
+
nolintlint:
|
|
61
|
+
require-explanation: true
|
|
62
|
+
require-specific: true
|
|
63
|
+
|
|
64
|
+
exclusions:
|
|
65
|
+
generated: lax
|
|
66
|
+
warn-unused: true
|
|
67
|
+
presets:
|
|
68
|
+
- std-error-handling
|
|
69
|
+
- common-false-positives
|
|
70
|
+
rules:
|
|
71
|
+
# Test code legitimately ignores errors, uses math/rand, repeats strings.
|
|
72
|
+
- path: '_test\.go'
|
|
73
|
+
linters:
|
|
74
|
+
- bodyclose
|
|
75
|
+
- gosec
|
|
76
|
+
- errcheck
|
|
77
|
+
- goconst
|
|
78
|
+
|
|
79
|
+
issues:
|
|
80
|
+
max-same-issues: 50
|
|
81
|
+
max-issues-per-linter: 0
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"printWidth": 100,
|
|
3
|
+
"tabWidth": 2,
|
|
4
|
+
"useTabs": false,
|
|
5
|
+
"semi": true,
|
|
6
|
+
"singleQuote": true,
|
|
7
|
+
"quoteProps": "as-needed",
|
|
8
|
+
"jsxSingleQuote": false,
|
|
9
|
+
"trailingComma": "all",
|
|
10
|
+
"bracketSpacing": true,
|
|
11
|
+
"bracketSameLine": false,
|
|
12
|
+
"arrowParens": "always",
|
|
13
|
+
"endOfLine": "lf"
|
|
14
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Generated by gimme-the-lint — TFLint (recommended tier)
|
|
2
|
+
#
|
|
3
|
+
# Uses only the bundled "terraform" ruleset, which ships inside the TFLint
|
|
4
|
+
# binary. No network access and no `tflint --init` required — works offline.
|
|
5
|
+
#
|
|
6
|
+
# For deeper, cloud-provider-specific checks (AWS / GCP / Azure best practices),
|
|
7
|
+
# add the matching provider plugin block and run `tflint --init` once. Those
|
|
8
|
+
# plugins require network access — do NOT add them for air-gapped installs.
|
|
9
|
+
#
|
|
10
|
+
# plugin "aws" {
|
|
11
|
+
# enabled = true
|
|
12
|
+
# version = "0.47.0"
|
|
13
|
+
# source = "github.com/terraform-linters/tflint-ruleset-aws"
|
|
14
|
+
# }
|
|
15
|
+
|
|
16
|
+
tflint {
|
|
17
|
+
required_version = ">= 0.50"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
config {
|
|
21
|
+
# "local" evaluates local module calls; "none" skips module evaluation.
|
|
22
|
+
call_module_type = "local"
|
|
23
|
+
# Set true to treat findings as warnings during initial rollout.
|
|
24
|
+
force = false
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
plugin "terraform" {
|
|
28
|
+
enabled = true
|
|
29
|
+
# "recommended" — Terraform-language hygiene. Use "all" for the stricter
|
|
30
|
+
# set (naming conventions, documented variables/outputs, module structure).
|
|
31
|
+
preset = "recommended"
|
|
32
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/latest/schema.json",
|
|
3
|
+
"vcs": {
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"clientKind": "git",
|
|
6
|
+
"useIgnoreFile": true,
|
|
7
|
+
"defaultBranch": "main"
|
|
8
|
+
},
|
|
9
|
+
"files": {
|
|
10
|
+
"ignoreUnknown": false,
|
|
11
|
+
"maxSize": 2097152
|
|
12
|
+
},
|
|
13
|
+
"formatter": {
|
|
14
|
+
"enabled": true,
|
|
15
|
+
"indentStyle": "space",
|
|
16
|
+
"indentWidth": 2,
|
|
17
|
+
"lineWidth": 100,
|
|
18
|
+
"lineEnding": "lf"
|
|
19
|
+
},
|
|
20
|
+
"javascript": {
|
|
21
|
+
"formatter": {
|
|
22
|
+
"quoteStyle": "double",
|
|
23
|
+
"jsxQuoteStyle": "double",
|
|
24
|
+
"quoteProperties": "asNeeded",
|
|
25
|
+
"trailingCommas": "all",
|
|
26
|
+
"semicolons": "always",
|
|
27
|
+
"arrowParentheses": "always",
|
|
28
|
+
"bracketSameLine": false
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"json": {
|
|
32
|
+
"formatter": {
|
|
33
|
+
"trailingCommas": "none"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"linter": {
|
|
37
|
+
"enabled": true,
|
|
38
|
+
"rules": {
|
|
39
|
+
"recommended": true,
|
|
40
|
+
"correctness": {
|
|
41
|
+
"noUnresolvedImports": "error"
|
|
42
|
+
},
|
|
43
|
+
"suspicious": {
|
|
44
|
+
"noConsole": {
|
|
45
|
+
"level": "warn",
|
|
46
|
+
"options": { "allow": ["error", "warn", "info"] }
|
|
47
|
+
},
|
|
48
|
+
"noImportCycles": "warn",
|
|
49
|
+
"noUnusedExpressions": "error"
|
|
50
|
+
},
|
|
51
|
+
"style": {
|
|
52
|
+
"useForOf": "warn",
|
|
53
|
+
"useNamingConvention": "warn",
|
|
54
|
+
"useConsistentTypeDefinitions": {
|
|
55
|
+
"level": "warn",
|
|
56
|
+
"options": { "define": "interface" }
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"complexity": {
|
|
60
|
+
"noExcessiveCognitiveComplexity": {
|
|
61
|
+
"level": "warn",
|
|
62
|
+
"options": { "maxAllowedComplexity": 15 }
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"performance": {
|
|
66
|
+
"noDelete": "warn"
|
|
67
|
+
},
|
|
68
|
+
"security": {
|
|
69
|
+
"noBlankTarget": "error",
|
|
70
|
+
"noDangerouslySetInnerHtml": "error",
|
|
71
|
+
"noDangerouslySetInnerHtmlWithChildren": "error",
|
|
72
|
+
"noGlobalEval": "error",
|
|
73
|
+
"noSecrets": "warn"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"overrides": [
|
|
78
|
+
{
|
|
79
|
+
"includes": ["**/*.test.*", "**/*.spec.*", "**/test/**"],
|
|
80
|
+
"linter": {
|
|
81
|
+
"rules": {
|
|
82
|
+
"suspicious": {
|
|
83
|
+
"noConsole": "off",
|
|
84
|
+
"noImportCycles": "off"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Generated by gimme-the-lint — Clippy lint levels
|
|
2
|
+
#
|
|
3
|
+
# Clippy lint GROUPS cannot be enabled from clippy.toml. The canonical
|
|
4
|
+
# mechanism (Cargo 1.74+) is the [lints.clippy] table in Cargo.toml.
|
|
5
|
+
# gimme-the-lint appends this block to your Cargo.toml only if no [lints]
|
|
6
|
+
# table already exists. For a workspace, put it under
|
|
7
|
+
# [workspace.lints.clippy] in the root Cargo.toml and add `[lints]
|
|
8
|
+
# workspace = true` to each member crate instead.
|
|
9
|
+
|
|
10
|
+
[lints.clippy]
|
|
11
|
+
# priority = -1 lets the individual "allow" overrides below win.
|
|
12
|
+
pedantic = { level = "warn", priority = -1 }
|
|
13
|
+
cargo = { level = "warn", priority = -1 }
|
|
14
|
+
|
|
15
|
+
# Pedantic lints that are too noisy in idiomatic Rust — allowed back.
|
|
16
|
+
module_name_repetitions = "allow" # fires on config::Config, error::Error, ...
|
|
17
|
+
must_use_candidate = "allow" # demands #[must_use] almost everywhere
|
|
18
|
+
missing_errors_doc = "allow" # requires an # Errors doc section
|
|
19
|
+
missing_panics_doc = "allow" # requires a # Panics doc section
|
|
20
|
+
default_trait_access = "allow" # Default::default() vs T::default() — style
|
|
21
|
+
map_unwrap_or = "allow" # map_or style — team preference
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Generated by gimme-the-lint — Clippy threshold tuning (recommended tier)
|
|
2
|
+
#
|
|
3
|
+
# clippy.toml only tunes lint PARAMETERS. It cannot enable lint groups —
|
|
4
|
+
# that is done via the [lints.clippy] table in Cargo.toml (see the
|
|
5
|
+
# clippy-cargo-lints snippet gimme-the-lint also provides).
|
|
6
|
+
|
|
7
|
+
# Rust style guide suggests 6; Clippy's default is 7.
|
|
8
|
+
too-many-arguments-threshold = 6
|
|
9
|
+
|
|
10
|
+
# Default is 100; 80 is a sensible opinionated ceiling.
|
|
11
|
+
too-many-lines-threshold = 80
|
|
12
|
+
|
|
13
|
+
# Default is 25; 15 is a solid complexity gate.
|
|
14
|
+
cognitive-complexity-threshold = 15
|
|
15
|
+
|
|
16
|
+
# Allow unwrap/expect/indexing in tests without per-file attributes.
|
|
17
|
+
allow-unwrap-in-tests = true
|
|
18
|
+
allow-expect-in-tests = true
|
|
19
|
+
allow-indexing-slicing-in-tests = true
|
|
20
|
+
|
|
21
|
+
# Set to your project's minimum supported Rust version so lint
|
|
22
|
+
# suggestions stay compatible.
|
|
23
|
+
msrv = "1.74"
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
// ESLint v9 Flat Config - Generated by gimme-the-lint
|
|
2
|
-
// Customize this file for your project
|
|
2
|
+
// Customize this file for your project.
|
|
3
|
+
//
|
|
4
|
+
// Ships security linting (eslint-plugin-security + eslint-plugin-no-secrets)
|
|
5
|
+
// and is Prettier-compatible (eslint-config-prettier disables conflicting
|
|
6
|
+
// stylistic rules). Dev deps beyond the React/TS set: eslint-plugin-security,
|
|
7
|
+
// eslint-plugin-no-secrets, eslint-config-prettier, prettier.
|
|
3
8
|
|
|
4
9
|
import js from '@eslint/js'
|
|
5
10
|
import globals from 'globals'
|
|
@@ -9,6 +14,9 @@ import reactRefresh from 'eslint-plugin-react-refresh'
|
|
|
9
14
|
import importPlugin from 'eslint-plugin-import'
|
|
10
15
|
import tseslint from '@typescript-eslint/eslint-plugin'
|
|
11
16
|
import tsparser from '@typescript-eslint/parser'
|
|
17
|
+
import security from 'eslint-plugin-security'
|
|
18
|
+
import noSecrets from 'eslint-plugin-no-secrets'
|
|
19
|
+
import prettier from 'eslint-config-prettier'
|
|
12
20
|
|
|
13
21
|
export default [
|
|
14
22
|
{ ignores: ['dist', 'build', 'node_modules', 'vite.config.ts'] },
|
|
@@ -44,6 +52,8 @@ export default [
|
|
|
44
52
|
'react-hooks': reactHooks,
|
|
45
53
|
'react-refresh': reactRefresh,
|
|
46
54
|
'import': importPlugin,
|
|
55
|
+
security,
|
|
56
|
+
'no-secrets': noSecrets,
|
|
47
57
|
},
|
|
48
58
|
rules: {
|
|
49
59
|
...js.configs.recommended.rules,
|
|
@@ -67,6 +77,9 @@ export default [
|
|
|
67
77
|
'import/no-cycle': 'error',
|
|
68
78
|
'import/no-self-import': 'error',
|
|
69
79
|
'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
|
|
80
|
+
// Security rules — unsafe APIs + hardcoded-secret detection
|
|
81
|
+
...security.configs.recommended.rules,
|
|
82
|
+
'no-secrets/no-secrets': ['warn', { tolerance: 4.2 }],
|
|
70
83
|
'react/no-unescaped-entities': 'off',
|
|
71
84
|
'react/prop-types': 'off',
|
|
72
85
|
},
|
|
@@ -93,6 +106,8 @@ export default [
|
|
|
93
106
|
'react-refresh': reactRefresh,
|
|
94
107
|
'import': importPlugin,
|
|
95
108
|
'@typescript-eslint': tseslint,
|
|
109
|
+
security,
|
|
110
|
+
'no-secrets': noSecrets,
|
|
96
111
|
},
|
|
97
112
|
rules: {
|
|
98
113
|
...js.configs.recommended.rules,
|
|
@@ -116,9 +131,16 @@ export default [
|
|
|
116
131
|
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
|
|
117
132
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
118
133
|
'@typescript-eslint/no-non-null-assertion': 'warn',
|
|
134
|
+
// Security rules — unsafe APIs + hardcoded-secret detection
|
|
135
|
+
...security.configs.recommended.rules,
|
|
136
|
+
'no-secrets/no-secrets': ['warn', { tolerance: 4.2 }],
|
|
119
137
|
'react/no-unescaped-entities': 'off',
|
|
120
138
|
'no-unused-vars': 'off',
|
|
121
139
|
'react/prop-types': 'off',
|
|
122
140
|
},
|
|
123
141
|
},
|
|
142
|
+
|
|
143
|
+
// eslint-config-prettier — disables stylistic rules that conflict with
|
|
144
|
+
// Prettier. Keep this LAST so it overrides earlier formatting rules.
|
|
145
|
+
prettier,
|
|
124
146
|
];
|
|
@@ -18,6 +18,10 @@ select = [
|
|
|
18
18
|
"I", # isort
|
|
19
19
|
"B", # flake8-bugbear
|
|
20
20
|
"UP", # pyupgrade
|
|
21
|
+
"S", # flake8-bandit — security (hardcoded passwords, SQL injection, SSL misuse)
|
|
22
|
+
"C4", # flake8-comprehensions
|
|
23
|
+
"SIM", # flake8-simplify
|
|
24
|
+
"RUF", # Ruff-specific rules
|
|
21
25
|
]
|
|
22
26
|
ignore = [
|
|
23
27
|
"E501", # line too long (handled by formatter)
|
|
@@ -26,6 +30,12 @@ ignore = [
|
|
|
26
30
|
[tool.ruff.lint.isort]
|
|
27
31
|
known-first-party = ["app"]
|
|
28
32
|
|
|
33
|
+
[tool.ruff.lint.per-file-ignores]
|
|
34
|
+
# Tests legitimately use `assert` and may hold dummy credentials/fixtures.
|
|
35
|
+
"tests/**" = ["S101", "S105", "S106"]
|
|
36
|
+
"**/test_*.py" = ["S101", "S105", "S106"]
|
|
37
|
+
"**/*_test.py" = ["S101", "S105", "S106"]
|
|
38
|
+
|
|
29
39
|
[tool.ruff.format]
|
|
30
40
|
quote-style = "double"
|
|
31
41
|
indent-style = "space"
|