@codacy/tools-reek-6 0.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/LICENSE +21 -0
- package/README.md +69 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +883 -0
- package/package.json +31 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Codacy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# @codacy/tools-reek-6
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Overview](#overview)
|
|
6
|
+
- [Updating patterns](#updating-patterns)
|
|
7
|
+
- [Updating the Reek version](#updating-the-reek-version)
|
|
8
|
+
- [Development](#development)
|
|
9
|
+
- [Notes for maintainers](#notes-for-maintainers)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
Ruby code smell detector using the [Reek](https://github.com/troessner/reek) gem. Uses the CLI execution strategy -- spawns `reek --format json` via `spawnTool()` and parses its JSON output.
|
|
16
|
+
|
|
17
|
+
| Property | Value |
|
|
18
|
+
| ------------- | -------------------------------------- |
|
|
19
|
+
| Tool ID | `Reek` |
|
|
20
|
+
| Codacy UUID | `ec8f6265-18e8-478a-a295-7a4c09fa2171` |
|
|
21
|
+
| Strategy | CLI |
|
|
22
|
+
| Languages | Ruby |
|
|
23
|
+
| Binary | `reek` (installed via `gem install`) |
|
|
24
|
+
| File patterns | `**/*.rb` |
|
|
25
|
+
|
|
26
|
+
## Updating patterns
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Re-fetch pattern metadata from the Codacy API
|
|
30
|
+
pnpm prefetch
|
|
31
|
+
|
|
32
|
+
# Commit the result
|
|
33
|
+
git add src/patterns.json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The `prefetch` script calls `packages/tools/scripts/fetch-patterns.mjs` with the tool UUID to download pattern metadata (IDs, categories, severity levels) from the Codacy API.
|
|
37
|
+
|
|
38
|
+
Pattern IDs are bare smell type names (no prefix): `FeatureEnvy`, `TooManyStatements`, `UncommunicativeMethodName`, etc.
|
|
39
|
+
|
|
40
|
+
## Updating the Reek version
|
|
41
|
+
|
|
42
|
+
1. Update `REEK_VERSION` in `src/adapter.ts`
|
|
43
|
+
2. Run `pnpm prefetch` to check for new/removed smells
|
|
44
|
+
3. Run `pnpm test` to verify compatibility
|
|
45
|
+
4. If the major version changes, create a new adapter package (`reek-7/`)
|
|
46
|
+
|
|
47
|
+
## Development
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pnpm build # Build with tsup
|
|
51
|
+
pnpm test # Run tests (requires ruby + reek in PATH for integration tests)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
To install reek locally for testing:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
gem install reek -v 6.5.0
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Notes for maintainers
|
|
61
|
+
|
|
62
|
+
- Reek requires Ruby >= 2.7 as a runtime. The adapter uses `ensureRuby()` to download a portable Ruby if no system Ruby is found.
|
|
63
|
+
- Gems are installed in an isolated directory at `~/.codacy/runtimes/reek-6/gems/` via `GEM_HOME`/`GEM_PATH` environment variables.
|
|
64
|
+
- Output format is a flat JSON array of smell objects, each with `smell_type`, `source`, `lines[]`, `message`, and `context`.
|
|
65
|
+
- The `lines` field is an array -- a single smell can span multiple lines. The adapter uses `lines[0]` as the primary line number.
|
|
66
|
+
- Exit codes: 0 = no smells, 2 = smells found (both success), 1 = runtime error.
|
|
67
|
+
- Pattern IDs in `patterns.json` have no prefix -- they are bare smell type names matching `smell_type` from Reek's JSON output.
|
|
68
|
+
- Pattern filtering is post-hoc: Reek runs against all provided files, then output is filtered by the enabled patterns set.
|
|
69
|
+
- Config file detection supports `.reek.yml` and `config.reek`. When a local config is found and `useLocalConfigurationFile` is true, it is passed via `--config <path>`.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,883 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
default: () => index_default
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
var import_tooling2 = require("@codacy/tooling");
|
|
37
|
+
|
|
38
|
+
// src/adapter.ts
|
|
39
|
+
var import_tooling = require("@codacy/tooling");
|
|
40
|
+
var import_path = __toESM(require("path"));
|
|
41
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
42
|
+
|
|
43
|
+
// src/patterns.json
|
|
44
|
+
var patterns_default = [
|
|
45
|
+
{
|
|
46
|
+
id: "Attribute",
|
|
47
|
+
category: "BestPractice",
|
|
48
|
+
severityLevel: "Warning",
|
|
49
|
+
title: "Avoid Public Writable Attributes",
|
|
50
|
+
enabled: true,
|
|
51
|
+
parameters: [
|
|
52
|
+
{
|
|
53
|
+
name: "exclude",
|
|
54
|
+
default: "[]",
|
|
55
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "BooleanParameter",
|
|
61
|
+
category: "Complexity",
|
|
62
|
+
severityLevel: "Warning",
|
|
63
|
+
title: "Avoid Boolean Parameter",
|
|
64
|
+
enabled: true,
|
|
65
|
+
parameters: [
|
|
66
|
+
{
|
|
67
|
+
name: "exclude",
|
|
68
|
+
default: "[]",
|
|
69
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "ClassVariable",
|
|
75
|
+
category: "BestPractice",
|
|
76
|
+
severityLevel: "Warning",
|
|
77
|
+
title: "Avoid Class Variables",
|
|
78
|
+
enabled: true,
|
|
79
|
+
parameters: [
|
|
80
|
+
{
|
|
81
|
+
name: "exclude",
|
|
82
|
+
default: "[]",
|
|
83
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: "ControlParameter",
|
|
89
|
+
category: "Complexity",
|
|
90
|
+
severityLevel: "Warning",
|
|
91
|
+
title: "Avoid Control Parameter",
|
|
92
|
+
enabled: true,
|
|
93
|
+
parameters: [
|
|
94
|
+
{
|
|
95
|
+
name: "exclude",
|
|
96
|
+
default: "[]",
|
|
97
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "DataClump",
|
|
103
|
+
category: "Complexity",
|
|
104
|
+
severityLevel: "Warning",
|
|
105
|
+
title: "Detect Data Clumps in Method Parameters",
|
|
106
|
+
enabled: true,
|
|
107
|
+
parameters: [
|
|
108
|
+
{
|
|
109
|
+
name: "exclude",
|
|
110
|
+
default: "[]",
|
|
111
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "max_copies",
|
|
115
|
+
default: "2",
|
|
116
|
+
description: "The maximum number of methods that are permitted to take the same group of parameters. Defaults to 2."
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "min_clump_size",
|
|
120
|
+
default: "2",
|
|
121
|
+
description: "The smallest number of parameters that can be reported as a clump. Defaults to 2."
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: "DuplicateMethodCall",
|
|
127
|
+
category: "Performance",
|
|
128
|
+
severityLevel: "Warning",
|
|
129
|
+
title: "Avoid Duplicate Method Calls Within a Method",
|
|
130
|
+
enabled: false,
|
|
131
|
+
parameters: [
|
|
132
|
+
{
|
|
133
|
+
name: "allow_calls",
|
|
134
|
+
default: "[]",
|
|
135
|
+
description: "Ignores any context who matches it."
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: "exclude",
|
|
139
|
+
default: "[]",
|
|
140
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: "max_calls",
|
|
144
|
+
default: "1",
|
|
145
|
+
description: "The maximum number of duplicate calls allowed within a method. Defaults to 1."
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: "FeatureEnvy",
|
|
151
|
+
category: "BestPractice",
|
|
152
|
+
severityLevel: "Warning",
|
|
153
|
+
title: "Detect Feature Envy",
|
|
154
|
+
enabled: true,
|
|
155
|
+
parameters: [
|
|
156
|
+
{
|
|
157
|
+
name: "exclude",
|
|
158
|
+
default: "[]",
|
|
159
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
160
|
+
}
|
|
161
|
+
]
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: "InstanceVariableAssumption",
|
|
165
|
+
category: "BestPractice",
|
|
166
|
+
severityLevel: "Warning",
|
|
167
|
+
title: "Avoid Instance Variable Assumption",
|
|
168
|
+
enabled: true,
|
|
169
|
+
parameters: [
|
|
170
|
+
{
|
|
171
|
+
name: "exclude",
|
|
172
|
+
default: "[]",
|
|
173
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: "IrresponsibleModule",
|
|
179
|
+
category: "Documentation",
|
|
180
|
+
severityLevel: "Info",
|
|
181
|
+
title: "Enforce Descriptive Comments on Modules and Classes",
|
|
182
|
+
enabled: true,
|
|
183
|
+
parameters: [
|
|
184
|
+
{
|
|
185
|
+
name: "exclude",
|
|
186
|
+
default: "[]",
|
|
187
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
188
|
+
}
|
|
189
|
+
]
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
id: "LongYieldList",
|
|
193
|
+
category: "Complexity",
|
|
194
|
+
severityLevel: "Warning",
|
|
195
|
+
title: "Avoid Long Yield List",
|
|
196
|
+
enabled: true,
|
|
197
|
+
parameters: [
|
|
198
|
+
{
|
|
199
|
+
name: "exclude",
|
|
200
|
+
default: "[]",
|
|
201
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: "max_params",
|
|
205
|
+
default: "3",
|
|
206
|
+
description: "The maximum number of parameters allowed in a method or block before a warning is issued. Defaults to 3."
|
|
207
|
+
}
|
|
208
|
+
]
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
id: "ManualDispatch",
|
|
212
|
+
category: "BestPractice",
|
|
213
|
+
severityLevel: "Warning",
|
|
214
|
+
title: "Avoid Manual Dispatch",
|
|
215
|
+
enabled: true,
|
|
216
|
+
parameters: [
|
|
217
|
+
{
|
|
218
|
+
name: "exclude",
|
|
219
|
+
default: "[]",
|
|
220
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
221
|
+
}
|
|
222
|
+
]
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
id: "MissingSafeMethod",
|
|
226
|
+
category: "BestPractice",
|
|
227
|
+
severityLevel: "Warning",
|
|
228
|
+
title: "Detect Missing Safe Method",
|
|
229
|
+
enabled: true,
|
|
230
|
+
parameters: [
|
|
231
|
+
{
|
|
232
|
+
name: "exclude",
|
|
233
|
+
default: "[]",
|
|
234
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
235
|
+
}
|
|
236
|
+
]
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
id: "ModuleInitialize",
|
|
240
|
+
category: "BestPractice",
|
|
241
|
+
severityLevel: "Warning",
|
|
242
|
+
title: "Avoid defining initialize method in modules",
|
|
243
|
+
enabled: true,
|
|
244
|
+
parameters: [
|
|
245
|
+
{
|
|
246
|
+
name: "exclude",
|
|
247
|
+
default: "[]",
|
|
248
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
249
|
+
}
|
|
250
|
+
]
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
id: "NestedIterators",
|
|
254
|
+
category: "Complexity",
|
|
255
|
+
severityLevel: "Warning",
|
|
256
|
+
title: "Avoid Nested Iterators",
|
|
257
|
+
enabled: true,
|
|
258
|
+
parameters: [
|
|
259
|
+
{
|
|
260
|
+
name: "exclude",
|
|
261
|
+
default: "[]",
|
|
262
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
name: "ignore_iterators",
|
|
266
|
+
default: "[]",
|
|
267
|
+
description: "List of iterators to be excluded from the smell check. Includes only tap at the moment"
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: "max_allowed_nesting",
|
|
271
|
+
default: "1",
|
|
272
|
+
description: "The maximum depth of nested iterators. Defaults to 1"
|
|
273
|
+
}
|
|
274
|
+
]
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
id: "NilCheck",
|
|
278
|
+
category: "BestPractice",
|
|
279
|
+
severityLevel: "Warning",
|
|
280
|
+
title: "Avoid Nil Checks",
|
|
281
|
+
enabled: true,
|
|
282
|
+
parameters: [
|
|
283
|
+
{
|
|
284
|
+
name: "exclude",
|
|
285
|
+
default: "[]",
|
|
286
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
287
|
+
}
|
|
288
|
+
]
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
id: "RepeatedConditional",
|
|
292
|
+
category: "Complexity",
|
|
293
|
+
severityLevel: "Warning",
|
|
294
|
+
title: "Avoid Repeated Conditional",
|
|
295
|
+
enabled: true,
|
|
296
|
+
parameters: [
|
|
297
|
+
{
|
|
298
|
+
name: "exclude",
|
|
299
|
+
default: "[]",
|
|
300
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
name: "max_ifs",
|
|
304
|
+
default: "2",
|
|
305
|
+
description: "The maximum number of identical conditional tests permitted before Reek raises a warning. Defaults to 2."
|
|
306
|
+
}
|
|
307
|
+
]
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
id: "SubclassedFromCoreClass",
|
|
311
|
+
category: "BestPractice",
|
|
312
|
+
severityLevel: "Warning",
|
|
313
|
+
title: "Avoid Subclassing Core Classes",
|
|
314
|
+
enabled: true,
|
|
315
|
+
parameters: [
|
|
316
|
+
{
|
|
317
|
+
name: "exclude",
|
|
318
|
+
default: "[]",
|
|
319
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
320
|
+
}
|
|
321
|
+
]
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
id: "TooManyConstants",
|
|
325
|
+
category: "Complexity",
|
|
326
|
+
severityLevel: "Warning",
|
|
327
|
+
title: "Avoid Too Many Constants in a Class",
|
|
328
|
+
enabled: true,
|
|
329
|
+
parameters: [
|
|
330
|
+
{
|
|
331
|
+
name: "exclude",
|
|
332
|
+
default: "[]",
|
|
333
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
name: "max_constants",
|
|
337
|
+
default: "5",
|
|
338
|
+
description: "The maximum number of constants that are permitted. Defaults to 5"
|
|
339
|
+
}
|
|
340
|
+
]
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
id: "TooManyInstanceVariables",
|
|
344
|
+
category: "Complexity",
|
|
345
|
+
severityLevel: "Warning",
|
|
346
|
+
title: "Avoid Too Many Instance Variables",
|
|
347
|
+
enabled: true,
|
|
348
|
+
parameters: [
|
|
349
|
+
{
|
|
350
|
+
name: "exclude",
|
|
351
|
+
default: "[]",
|
|
352
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
name: "max_instance_variables",
|
|
356
|
+
default: "4",
|
|
357
|
+
description: "The maximum number of instance variables that are permitted. Defaults to 4"
|
|
358
|
+
}
|
|
359
|
+
]
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
id: "TooManyMethods",
|
|
363
|
+
category: "Complexity",
|
|
364
|
+
severityLevel: "Warning",
|
|
365
|
+
title: "Detect Too Many Methods in Class",
|
|
366
|
+
enabled: true,
|
|
367
|
+
parameters: [
|
|
368
|
+
{
|
|
369
|
+
name: "exclude",
|
|
370
|
+
default: "[]",
|
|
371
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
name: "max_methods",
|
|
375
|
+
default: "15",
|
|
376
|
+
description: "The maximum number of methods that are permitted. Defaults to 15"
|
|
377
|
+
}
|
|
378
|
+
]
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
id: "TooManyStatements",
|
|
382
|
+
category: "Complexity",
|
|
383
|
+
severityLevel: "Warning",
|
|
384
|
+
title: "Avoid Methods with Too Many Statements",
|
|
385
|
+
enabled: true,
|
|
386
|
+
parameters: [
|
|
387
|
+
{
|
|
388
|
+
name: "exclude",
|
|
389
|
+
default: "[]",
|
|
390
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
name: "max_statements",
|
|
394
|
+
default: "5",
|
|
395
|
+
description: "The maximum number of statements allowed in a method before a warning is issued. Defaults to 5."
|
|
396
|
+
}
|
|
397
|
+
]
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
id: "UncommunicativeMethodName",
|
|
401
|
+
category: "Comprehensibility",
|
|
402
|
+
severityLevel: "Warning",
|
|
403
|
+
title: "Avoid Uncommunicative Method Names",
|
|
404
|
+
enabled: false,
|
|
405
|
+
parameters: [
|
|
406
|
+
{
|
|
407
|
+
name: "accept",
|
|
408
|
+
default: "[]",
|
|
409
|
+
description: "The set of names that Reek will accept (and not report) even if they match one of the reject expressions."
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: "exclude",
|
|
413
|
+
default: "[]",
|
|
414
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
415
|
+
}
|
|
416
|
+
]
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
id: "UncommunicativeModuleName",
|
|
420
|
+
category: "Comprehensibility",
|
|
421
|
+
severityLevel: "Info",
|
|
422
|
+
title: "Avoid Uncommunicative Module Names",
|
|
423
|
+
enabled: false,
|
|
424
|
+
parameters: [
|
|
425
|
+
{
|
|
426
|
+
name: "accept",
|
|
427
|
+
default: "[]",
|
|
428
|
+
description: "The set of names that Reek will accept (and not report) even if they match one of the reject expressions. Empty by default."
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
name: "exclude",
|
|
432
|
+
default: "[]",
|
|
433
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
434
|
+
}
|
|
435
|
+
]
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
id: "UncommunicativeParameterName",
|
|
439
|
+
category: "CodeStyle",
|
|
440
|
+
severityLevel: "Info",
|
|
441
|
+
title: "Avoid Uncommunicative Parameter Names",
|
|
442
|
+
enabled: false,
|
|
443
|
+
parameters: [
|
|
444
|
+
{
|
|
445
|
+
name: "accept",
|
|
446
|
+
default: "[]",
|
|
447
|
+
description: "The set of names that Reek will accept (and not report) even if they match one of the reject expressions."
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
name: "exclude",
|
|
451
|
+
default: "[]",
|
|
452
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
453
|
+
}
|
|
454
|
+
]
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
id: "UncommunicativeVariableName",
|
|
458
|
+
category: "Comprehensibility",
|
|
459
|
+
severityLevel: "Info",
|
|
460
|
+
title: "Avoid Uncommunicative Variable Names",
|
|
461
|
+
enabled: false,
|
|
462
|
+
parameters: [
|
|
463
|
+
{
|
|
464
|
+
name: "accept",
|
|
465
|
+
default: '["_"]',
|
|
466
|
+
description: "Names that will be accepted (not reported) even if they match one of the reject expressions. Defaults to _."
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
name: "exclude",
|
|
470
|
+
default: "[]",
|
|
471
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
472
|
+
}
|
|
473
|
+
]
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
id: "UnusedParameters",
|
|
477
|
+
category: "UnusedCode",
|
|
478
|
+
severityLevel: "Warning",
|
|
479
|
+
title: "Avoid Unused Parameters in Methods",
|
|
480
|
+
enabled: true,
|
|
481
|
+
parameters: [
|
|
482
|
+
{
|
|
483
|
+
name: "exclude",
|
|
484
|
+
default: "[]",
|
|
485
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
486
|
+
}
|
|
487
|
+
]
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
id: "UnusedPrivateMethod",
|
|
491
|
+
category: "UnusedCode",
|
|
492
|
+
severityLevel: "Warning",
|
|
493
|
+
title: "Avoid Unused Private Methods",
|
|
494
|
+
enabled: true,
|
|
495
|
+
parameters: [
|
|
496
|
+
{
|
|
497
|
+
name: "exclude",
|
|
498
|
+
default: "[]",
|
|
499
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
500
|
+
}
|
|
501
|
+
]
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
id: "UtilityFunction",
|
|
505
|
+
category: "BestPractice",
|
|
506
|
+
severityLevel: "Warning",
|
|
507
|
+
title: "Detect Utility Functions",
|
|
508
|
+
enabled: true,
|
|
509
|
+
parameters: [
|
|
510
|
+
{
|
|
511
|
+
name: "exclude",
|
|
512
|
+
default: "[]",
|
|
513
|
+
description: "Ignores any context whose full description matches any element of this array."
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
name: "public_methods_only",
|
|
517
|
+
default: "false",
|
|
518
|
+
description: "Disable this smell detector for non-public methods (which means 'private' and 'protected')"
|
|
519
|
+
}
|
|
520
|
+
]
|
|
521
|
+
}
|
|
522
|
+
];
|
|
523
|
+
|
|
524
|
+
// src/adapter.ts
|
|
525
|
+
var TOOL_ID = "Reek";
|
|
526
|
+
var ADAPTER_ID = "reek-6";
|
|
527
|
+
var REEK_VERSION = "6.5.0";
|
|
528
|
+
var RUBY_VERSION_RANGE = ">=2.7.0";
|
|
529
|
+
var CONFIG_FILE_CANDIDATES = [".reek.yml", "config.reek"];
|
|
530
|
+
var PATTERN_META = new Map(
|
|
531
|
+
patterns_default.map((p) => [
|
|
532
|
+
p.id,
|
|
533
|
+
{
|
|
534
|
+
category: p.category,
|
|
535
|
+
severity: p.severityLevel ?? "Info"
|
|
536
|
+
}
|
|
537
|
+
])
|
|
538
|
+
);
|
|
539
|
+
function ruleMetadata(smellType) {
|
|
540
|
+
return PATTERN_META.get(smellType) ?? { category: "CodeStyle", severity: "Info" };
|
|
541
|
+
}
|
|
542
|
+
function getLineContent(lines, lineNumber) {
|
|
543
|
+
return lines[lineNumber - 1] ?? "";
|
|
544
|
+
}
|
|
545
|
+
async function readFileLines(filePath) {
|
|
546
|
+
try {
|
|
547
|
+
const content = await import_promises.default.readFile(filePath, "utf-8");
|
|
548
|
+
return content.split("\n");
|
|
549
|
+
} catch {
|
|
550
|
+
return [];
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
function buildResult(startedAt, completedAt, issues, errors, filesAnalyzed) {
|
|
554
|
+
const durationMs = completedAt.getTime() - startedAt.getTime();
|
|
555
|
+
return {
|
|
556
|
+
metadata: {
|
|
557
|
+
startedAt: startedAt.toISOString(),
|
|
558
|
+
completedAt: completedAt.toISOString(),
|
|
559
|
+
durationMs
|
|
560
|
+
},
|
|
561
|
+
issues,
|
|
562
|
+
errors,
|
|
563
|
+
summary: {
|
|
564
|
+
toolId: TOOL_ID,
|
|
565
|
+
status: errors.some((e) => e.level === "error") ? "partial" : "success",
|
|
566
|
+
issueCount: issues.length,
|
|
567
|
+
errorCount: errors.length,
|
|
568
|
+
durationMs,
|
|
569
|
+
filesAnalyzed
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
async function resolveToolInvocation(ctx) {
|
|
574
|
+
const gemPaths = (0, import_tooling.getGemPaths)(ctx.globalDir, ADAPTER_ID);
|
|
575
|
+
const scriptPath = (0, import_tooling.resolveGemBinary)(gemPaths, "reek");
|
|
576
|
+
const portableRubyBin = (0, import_tooling.resolvePortableRubyBinary)(ctx.globalDir, "3.2.11");
|
|
577
|
+
try {
|
|
578
|
+
await import_promises.default.access(portableRubyBin);
|
|
579
|
+
await import_promises.default.access(scriptPath);
|
|
580
|
+
return { binary: portableRubyBin, prefixArgs: [scriptPath], rubyPath: portableRubyBin };
|
|
581
|
+
} catch {
|
|
582
|
+
}
|
|
583
|
+
const rubyInfo = await (0, import_tooling.findRuby)(RUBY_VERSION_RANGE).catch(() => null);
|
|
584
|
+
try {
|
|
585
|
+
await import_promises.default.access(scriptPath);
|
|
586
|
+
return {
|
|
587
|
+
binary: scriptPath,
|
|
588
|
+
prefixArgs: [],
|
|
589
|
+
rubyPath: rubyInfo?.path
|
|
590
|
+
};
|
|
591
|
+
} catch {
|
|
592
|
+
}
|
|
593
|
+
return { binary: "reek", prefixArgs: [], rubyPath: rubyInfo?.path };
|
|
594
|
+
}
|
|
595
|
+
var adapter = {
|
|
596
|
+
id: TOOL_ID,
|
|
597
|
+
displayName: "Reek",
|
|
598
|
+
preferredVersion: REEK_VERSION,
|
|
599
|
+
compatibleVersions: ">=6.0.0",
|
|
600
|
+
website: "https://github.com/troessner/reek",
|
|
601
|
+
languages: ["Ruby"],
|
|
602
|
+
filePatterns: ["**/*.rb"],
|
|
603
|
+
supportedOS: ["linux", "darwin", "win32"],
|
|
604
|
+
supportedArch: ["x64", "arm64"],
|
|
605
|
+
supportedRunners: ["local", "container"],
|
|
606
|
+
prerequisites: [
|
|
607
|
+
{
|
|
608
|
+
name: "Ruby",
|
|
609
|
+
versionRange: RUBY_VERSION_RANGE,
|
|
610
|
+
preferredVersion: "3.2",
|
|
611
|
+
checkCommand: "ruby --version"
|
|
612
|
+
}
|
|
613
|
+
],
|
|
614
|
+
/**
|
|
615
|
+
* Checks whether Reek is available (Ruby runtime + reek gem).
|
|
616
|
+
*
|
|
617
|
+
* Checks:
|
|
618
|
+
* 1. Ruby runtime via findRuby
|
|
619
|
+
* 2. Reek binary in the Codacy-managed gem directory or on PATH
|
|
620
|
+
*/
|
|
621
|
+
async checkAvailability(ctx) {
|
|
622
|
+
ctx.logger.debug("Checking availability", { toolId: TOOL_ID });
|
|
623
|
+
const rubyInfo = await (0, import_tooling.findRuby)(RUBY_VERSION_RANGE, ctx.logger).catch(() => null);
|
|
624
|
+
let resolvedRubyPath = rubyInfo?.path;
|
|
625
|
+
const dependencies = rubyInfo ? [{ name: "Ruby", available: true, version: rubyInfo.version, installation: "global" }] : [{ name: "Ruby", available: false, reason: `Ruby (${RUBY_VERSION_RANGE}) not found` }];
|
|
626
|
+
if (!rubyInfo) {
|
|
627
|
+
const rubyBin = (0, import_tooling.resolvePortableRubyBinary)(ctx.globalDir, "3.2.11");
|
|
628
|
+
try {
|
|
629
|
+
await import_promises.default.access(rubyBin);
|
|
630
|
+
resolvedRubyPath = rubyBin;
|
|
631
|
+
dependencies[0] = {
|
|
632
|
+
name: "Ruby",
|
|
633
|
+
available: true,
|
|
634
|
+
version: "3.2.11",
|
|
635
|
+
installation: "codacy"
|
|
636
|
+
};
|
|
637
|
+
} catch {
|
|
638
|
+
return {
|
|
639
|
+
available: false,
|
|
640
|
+
reason: "Ruby runtime not found (required for Reek)",
|
|
641
|
+
installHint: "Install Ruby 2.7+, or run with --install-dependencies for auto-download",
|
|
642
|
+
dependencies
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
const gemPaths = (0, import_tooling.getGemPaths)(ctx.globalDir, ADAPTER_ID);
|
|
647
|
+
const reekVersion = await (0, import_tooling.checkGemTool)(
|
|
648
|
+
gemPaths,
|
|
649
|
+
"reek",
|
|
650
|
+
"--version",
|
|
651
|
+
/reek\s+(\S+)/,
|
|
652
|
+
resolvedRubyPath,
|
|
653
|
+
ctx.logger
|
|
654
|
+
);
|
|
655
|
+
if (reekVersion) {
|
|
656
|
+
return {
|
|
657
|
+
available: true,
|
|
658
|
+
version: reekVersion,
|
|
659
|
+
path: (0, import_tooling.resolveGemBinary)(gemPaths, "reek"),
|
|
660
|
+
installation: "codacy",
|
|
661
|
+
dependencies
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
try {
|
|
665
|
+
const { stdout, exitCode } = await (0, import_tooling.spawnTool)("reek", ["--version"], {
|
|
666
|
+
cwd: ctx.repositoryRoot,
|
|
667
|
+
logger: ctx.logger
|
|
668
|
+
});
|
|
669
|
+
if (exitCode === 0) {
|
|
670
|
+
const match = stdout.match(/reek\s+(\S+)/);
|
|
671
|
+
if (match) {
|
|
672
|
+
return {
|
|
673
|
+
available: true,
|
|
674
|
+
version: match[1],
|
|
675
|
+
installation: "global",
|
|
676
|
+
dependencies
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
} catch {
|
|
681
|
+
}
|
|
682
|
+
return {
|
|
683
|
+
available: false,
|
|
684
|
+
reason: "Reek not found in Codacy gem directory or on PATH",
|
|
685
|
+
installHint: "Run with --install-dependencies to auto-install, or run: gem install reek",
|
|
686
|
+
dependencies
|
|
687
|
+
};
|
|
688
|
+
},
|
|
689
|
+
/**
|
|
690
|
+
* Installs Reek by ensuring a Ruby runtime is available and installing
|
|
691
|
+
* the reek gem into an isolated gem directory.
|
|
692
|
+
*/
|
|
693
|
+
async install(ctx) {
|
|
694
|
+
ctx.logger.info("Starting install", { toolId: TOOL_ID });
|
|
695
|
+
const ruby = await (0, import_tooling.ensureRuby)(ctx.globalDir, RUBY_VERSION_RANGE, ctx.logger);
|
|
696
|
+
if (!ruby) {
|
|
697
|
+
return {
|
|
698
|
+
success: false,
|
|
699
|
+
error: {
|
|
700
|
+
toolId: TOOL_ID,
|
|
701
|
+
phase: "requirementCheck",
|
|
702
|
+
kind: "MissingPrerequisite",
|
|
703
|
+
message: `Ruby (${RUBY_VERSION_RANGE}) could not be found or downloaded. Reek requires a Ruby runtime.`,
|
|
704
|
+
level: "error"
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
try {
|
|
709
|
+
const gemPaths = (0, import_tooling.getGemPaths)(ctx.globalDir, ADAPTER_ID);
|
|
710
|
+
ctx.logger.info(`Installing Reek ${REEK_VERSION} to ${gemPaths.gemHome}...`);
|
|
711
|
+
await (0, import_tooling.gemInstall)(gemPaths, ruby.path, [`reek:${REEK_VERSION}`], ctx.logger);
|
|
712
|
+
const installedPath = (0, import_tooling.resolveGemBinary)(gemPaths, "reek");
|
|
713
|
+
ctx.logger.info(`Installed Reek to ${installedPath}`);
|
|
714
|
+
return { success: true, installedPath, installedVersion: REEK_VERSION };
|
|
715
|
+
} catch (err) {
|
|
716
|
+
return {
|
|
717
|
+
success: false,
|
|
718
|
+
error: {
|
|
719
|
+
toolId: TOOL_ID,
|
|
720
|
+
phase: "toolInstall",
|
|
721
|
+
kind: "InstallError",
|
|
722
|
+
message: `Failed to install Reek: ${err instanceof Error ? err.message : String(err)}`,
|
|
723
|
+
level: "error"
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
},
|
|
728
|
+
/**
|
|
729
|
+
* Checks for Reek configuration files in the repository root.
|
|
730
|
+
* Looks for `.reek.yml` or `config.reek` (in that order).
|
|
731
|
+
*/
|
|
732
|
+
async checkLocalConfigurationFile(ctx) {
|
|
733
|
+
ctx.logger.debug("Checking for local config", { toolId: TOOL_ID });
|
|
734
|
+
for (const candidate of CONFIG_FILE_CANDIDATES) {
|
|
735
|
+
const candidatePath = import_path.default.join(ctx.repositoryRoot, candidate);
|
|
736
|
+
try {
|
|
737
|
+
await import_promises.default.access(candidatePath);
|
|
738
|
+
ctx.logger.debug("Local config found", { toolId: TOOL_ID, path: candidatePath });
|
|
739
|
+
return { found: true, path: candidatePath };
|
|
740
|
+
} catch {
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
return { found: false };
|
|
744
|
+
},
|
|
745
|
+
/**
|
|
746
|
+
* Runs Reek against the target Ruby files and converts the JSON output
|
|
747
|
+
* into Codacy Issue objects.
|
|
748
|
+
*
|
|
749
|
+
* Config resolution:
|
|
750
|
+
* - If `useLocalConfigurationFile` is true and a local config is found,
|
|
751
|
+
* passes `--config <path>` to reek.
|
|
752
|
+
* - Otherwise, reek runs with defaults and output is post-filtered by
|
|
753
|
+
* enabled patterns.
|
|
754
|
+
*
|
|
755
|
+
* Key details:
|
|
756
|
+
* - `--format json` produces a flat JSON array of smell objects.
|
|
757
|
+
* - Exit code 0 = no smells, 2 = smells found (both success), 1 = error.
|
|
758
|
+
* - The `lines` field is an array — we use `lines[0]` as the primary line.
|
|
759
|
+
* - Pattern IDs are bare smell type names (e.g. `FeatureEnvy`).
|
|
760
|
+
*
|
|
761
|
+
* Never throws — all errors are captured in the returned `errors` array.
|
|
762
|
+
*/
|
|
763
|
+
async analyze(ctx, codacyConfig) {
|
|
764
|
+
const startMs = Date.now();
|
|
765
|
+
ctx.logger.info("Starting analysis", { toolId: TOOL_ID, fileCount: ctx.targetFiles.length });
|
|
766
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
767
|
+
const issues = [];
|
|
768
|
+
const errors = [];
|
|
769
|
+
if (ctx.targetFiles.length === 0) {
|
|
770
|
+
return buildResult(startedAt, /* @__PURE__ */ new Date(), issues, errors, 0);
|
|
771
|
+
}
|
|
772
|
+
const invocation = await resolveToolInvocation(ctx);
|
|
773
|
+
const absoluteFiles = ctx.targetFiles.map((f) => import_path.default.resolve(ctx.repositoryRoot, f));
|
|
774
|
+
const args = [...invocation.prefixArgs, "--format", "json"];
|
|
775
|
+
const useLocal = codacyConfig.useLocalConfigurationFile && codacyConfig.localConfigurationFile;
|
|
776
|
+
if (useLocal) {
|
|
777
|
+
args.push("--config", codacyConfig.localConfigurationFile);
|
|
778
|
+
}
|
|
779
|
+
args.push(...absoluteFiles);
|
|
780
|
+
try {
|
|
781
|
+
const gemPaths = (0, import_tooling.getGemPaths)(ctx.globalDir, ADAPTER_ID);
|
|
782
|
+
const env = (0, import_tooling.buildGemEnv)(gemPaths, invocation.rubyPath);
|
|
783
|
+
const { stdout, stderr, exitCode } = await (0, import_tooling.spawnTool)(invocation.binary, args, {
|
|
784
|
+
cwd: ctx.repositoryRoot,
|
|
785
|
+
env,
|
|
786
|
+
logger: ctx.logger
|
|
787
|
+
});
|
|
788
|
+
if (exitCode !== 0 && exitCode !== 2) {
|
|
789
|
+
errors.push({
|
|
790
|
+
toolId: TOOL_ID,
|
|
791
|
+
phase: "toolInvoke",
|
|
792
|
+
kind: "NonZeroExit",
|
|
793
|
+
message: `Reek exited with code ${exitCode}${stderr ? `: ${stderr.trim()}` : ""}`,
|
|
794
|
+
level: "error"
|
|
795
|
+
});
|
|
796
|
+
return buildResult(startedAt, /* @__PURE__ */ new Date(), issues, errors, ctx.targetFiles.length);
|
|
797
|
+
}
|
|
798
|
+
if (!stdout.trim()) {
|
|
799
|
+
return buildResult(startedAt, /* @__PURE__ */ new Date(), issues, errors, ctx.targetFiles.length);
|
|
800
|
+
}
|
|
801
|
+
let smells;
|
|
802
|
+
try {
|
|
803
|
+
smells = JSON.parse(stdout);
|
|
804
|
+
} catch (err) {
|
|
805
|
+
errors.push({
|
|
806
|
+
toolId: TOOL_ID,
|
|
807
|
+
phase: "outputParse",
|
|
808
|
+
kind: "MalformedOutput",
|
|
809
|
+
message: `Failed to parse Reek JSON output: ${err instanceof Error ? err.message : String(err)}`,
|
|
810
|
+
level: "error"
|
|
811
|
+
});
|
|
812
|
+
return buildResult(startedAt, /* @__PURE__ */ new Date(), issues, errors, ctx.targetFiles.length);
|
|
813
|
+
}
|
|
814
|
+
if (!Array.isArray(smells)) {
|
|
815
|
+
errors.push({
|
|
816
|
+
toolId: TOOL_ID,
|
|
817
|
+
phase: "outputParse",
|
|
818
|
+
kind: "MalformedOutput",
|
|
819
|
+
message: "Reek output is not a JSON array",
|
|
820
|
+
level: "error"
|
|
821
|
+
});
|
|
822
|
+
return buildResult(startedAt, /* @__PURE__ */ new Date(), issues, errors, ctx.targetFiles.length);
|
|
823
|
+
}
|
|
824
|
+
const enabledPatterns = new Set(codacyConfig.patterns.map((p) => p.patternId));
|
|
825
|
+
const fileLineCache = /* @__PURE__ */ new Map();
|
|
826
|
+
for (const smell of smells) {
|
|
827
|
+
const patternId = smell.smell_type;
|
|
828
|
+
if (!patternId) continue;
|
|
829
|
+
if (enabledPatterns.size > 0 && !enabledPatterns.has(patternId)) continue;
|
|
830
|
+
let filePath = smell.source;
|
|
831
|
+
if (import_path.default.isAbsolute(filePath)) {
|
|
832
|
+
filePath = import_path.default.relative(ctx.repositoryRoot, filePath);
|
|
833
|
+
}
|
|
834
|
+
const line = smell.lines?.[0] ?? 1;
|
|
835
|
+
const meta = ruleMetadata(patternId);
|
|
836
|
+
const absPath = import_path.default.resolve(ctx.repositoryRoot, filePath);
|
|
837
|
+
if (!fileLineCache.has(absPath)) {
|
|
838
|
+
fileLineCache.set(absPath, await readFileLines(absPath));
|
|
839
|
+
}
|
|
840
|
+
const fileLines = fileLineCache.get(absPath);
|
|
841
|
+
issues.push({
|
|
842
|
+
filePath,
|
|
843
|
+
patternId,
|
|
844
|
+
toolId: TOOL_ID,
|
|
845
|
+
line,
|
|
846
|
+
message: smell.message,
|
|
847
|
+
lineContent: getLineContent(fileLines, line),
|
|
848
|
+
category: meta.category,
|
|
849
|
+
severity: meta.severity
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
const stderrFiltered = stderr.split("\n").filter((l) => l.trim()).join("\n").trim();
|
|
853
|
+
if (stderrFiltered) {
|
|
854
|
+
errors.push({
|
|
855
|
+
toolId: TOOL_ID,
|
|
856
|
+
phase: "outputParse",
|
|
857
|
+
kind: "ParseWarning",
|
|
858
|
+
message: `Reek stderr: ${stderrFiltered}`,
|
|
859
|
+
level: "warning"
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
} catch (invokeErr) {
|
|
863
|
+
errors.push({
|
|
864
|
+
toolId: TOOL_ID,
|
|
865
|
+
phase: "toolInvoke",
|
|
866
|
+
kind: "InvocationError",
|
|
867
|
+
message: `Failed to run Reek: ${invokeErr instanceof Error ? invokeErr.message : String(invokeErr)}`,
|
|
868
|
+
level: "error"
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
const durationMs = Date.now() - startMs;
|
|
872
|
+
ctx.logger.info("Analysis complete", {
|
|
873
|
+
toolId: TOOL_ID,
|
|
874
|
+
issueCount: issues.length,
|
|
875
|
+
errorCount: errors.length,
|
|
876
|
+
durationMs
|
|
877
|
+
});
|
|
878
|
+
return buildResult(startedAt, /* @__PURE__ */ new Date(), issues, errors, ctx.targetFiles.length);
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
|
|
882
|
+
// src/index.ts
|
|
883
|
+
var index_default = (0, import_tooling2.defineToolAdapter)(adapter);
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@codacy/tools-reek-6",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Reek adapter — CLI-mode Ruby code smell detector",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@codacy/tooling": "0.2.0"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "Codacy <support@codacy.com> (https://www.codacy.com)",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/codacy/analysis-cli.git",
|
|
18
|
+
"directory": "packages/tools/reek-6"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/codacy/analysis-cli",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=20.0.0"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup src/index.ts --format cjs --dts --clean",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"lint": "eslint src/",
|
|
28
|
+
"clean": "rm -rf dist",
|
|
29
|
+
"prefetch": "node ../scripts/fetch-patterns.mjs ec8f6265-18e8-478a-a295-7a4c09fa2171 src/patterns.json"
|
|
30
|
+
}
|
|
31
|
+
}
|