npm-groovy-lint 9.3.0 → 9.4.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  ## UNRELEASED
4
4
 
5
+ - Add your updates here :)
6
+
7
+ ## [9.4.1] 2022-01-22
8
+
9
+ - Upgrade node-sarif-builder to 2.0.1 and send npm-groovy-lint version in SARIF logs
10
+ - New range detection for rules:
11
+ - MethodReturnTypeRequired
12
+ - UnusedImport
13
+
14
+ ## [9.4.0] 2022-01-11
15
+
16
+ - Add [SARIF](https://sarifweb.azurewebsites.net/) output format using [node-sarif-builder](https://github.com/nvuillam/node-sarif-builder)
17
+
18
+ ## [9.3.2] 2022-01-09
19
+
20
+ - Upgrade cli-progress to avoid [colors lib boring but harmless hack](https://github.com/Marak/colors.js/issues/285)
21
+
22
+ ## [9.3.1] 2022-01-06
23
+
24
+ - Fix issue when used as module and with file containing spaces ([VsCodeGroovyLint #137](https://github.com/nvuillam/vscode-groovy-lint/issues/137))
25
+
5
26
  ## [9.3.0] 2021-12-29
6
27
 
7
28
  - Upgrade log4j dependencies because of [security issue](https://nvd.nist.gov/vuln/detail/CVE-2021-44832).
package/README.md CHANGED
@@ -47,7 +47,7 @@ Any **question**, **problem** or **enhancement request** ? Ask [**here**](https:
47
47
  |-------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
48
48
  | -p<br/> --path | String | Directory containing the files to lint<br/> Example: `./path/to/my/groovy/files` |
49
49
  | -f<br/> --files | String | Comma-separated list of Ant-style file patterns specifying files that must be included.<br/> Default: `"**/*.groovy,**/Jenkinsfile,**/*.gradle"`<br/>Examples:<br/> - `"**/Jenkinsfile"`<br/> - `"**/*.groovy"`<br/> - `"**/*.gradle"`<br/> - `"**/mySingleFile.groovy"` |
50
- | -o<br/> --output | String | Output format (txt,json,html,xml), or path to a file with one of these extensions<br/> Default: `txt`<br/> Examples:<br/> - `"txt"`<br/> - `"json"`<br/> - `"./logs/myLintResults.txt"`<br/> - `"./logs/myLintResults.json"`<br/> - `"./logs/myLintResults.html"`<br/> - `"./logs/myLintResults.xml"` |
50
+ | -o<br/> --output | String | Output format (txt,json,sarif,html,xml), or path to a file with one of these extensions<br/> Default: `txt`<br/> Examples:<br/> - `"txt"`<br/> - `"json"`<br/> - `"./logs/myLintResults.txt"`<br/> - `"./logs/myLintResults.sarif"`<br/> - `"./logs/myLintResults.html"`<br/> - `"./logs/myLintResults.xml"` |
51
51
  | -l<br/> --loglevel | String | Log level (error,warning or info)<br/>Default: info |
52
52
  | --failon | String | Defines the error level where CLI will fail (return code = 1). error,warning,info or none. Each failure level includes the more critical ones. |
53
53
  | -c<br/> --config | String | Custom path to [GroovyLint config file](#Configuration), or preset config `recommended|recommended-jenkinsfile|all`<br/> Default: Browse current directory to find `.groovylintrc.json|js|yml|package.json` config file, or default npm-groovy-lint config if not defined.<br/>Note: command-line arguments have priority on config file properties |
@@ -304,7 +304,7 @@ class CodeNarcCaller {
304
304
  });
305
305
 
306
306
  if (this.serverStatus === "running") {
307
- console.log(c.green(`GroovyLint: Started CodeNarc Server`));
307
+ debug(c.green(`GroovyLint: Started CodeNarc Server`));
308
308
  return true;
309
309
  } else {
310
310
  return false;
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  const debug = require("debug")("npm-groovy-lint");
5
- const fse = require("fs-extra");
5
+ const fs = require("fs-extra");
6
6
  const os = require("os");
7
7
  const path = require("path");
8
8
  const { getConfigFileName } = require("./config.js");
@@ -29,14 +29,15 @@ async function prepareCodeNarcCall(options) {
29
29
  // If source option, create a temporary Groovy file
30
30
  if (options.source) {
31
31
  cnPath = path.resolve(os.tmpdir() + "/npm-groovy-lint");
32
- await fse.ensureDir(cnPath, { mode: "0777" });
32
+ await fs.ensureDir(cnPath, { mode: "0777" });
33
33
  // File path is sent (recommended): use it to create temp file name
34
34
  if (options.sourcefilepath) {
35
35
  const pathParse = path.parse(options.sourcefilepath);
36
36
  cnPath = cnPath + "/codeNarcTmpDir_" + Math.random();
37
- await fse.ensureDir(cnPath, { mode: "0777" });
38
- result.tmpGroovyFileName = path.resolve(cnPath + "/" + pathParse.base);
39
- cnFiles = "**/" + pathParse.base;
37
+ await fs.ensureDir(cnPath, { mode: "0777" });
38
+ const pathBase = pathParse.base.replace(/ /g, "_");
39
+ result.tmpGroovyFileName = path.resolve(cnPath + "/" + pathBase);
40
+ cnFiles = "**/" + pathBase;
40
41
  }
41
42
  // Use default random file name
42
43
  else {
@@ -45,7 +46,7 @@ async function prepareCodeNarcCall(options) {
45
46
  cnFiles = "**/" + tmpFileNm;
46
47
  }
47
48
 
48
- await fse.writeFile(result.tmpGroovyFileName, normalizeNewLines(options.source));
49
+ await fs.writeFile(result.tmpGroovyFileName, normalizeNewLines(options.source));
49
50
  debug(`CREATE GROOVY temp file ${result.tmpGroovyFileName} with input source, as CodeNarc requires physical files`);
50
51
  }
51
52
 
@@ -90,8 +91,14 @@ async function prepareCodeNarcCall(options) {
90
91
 
91
92
  // Output
92
93
  result.output = options.output.replace(/^"(.*)"$/, "$1");
93
- if (["txt", "json", "none"].includes(result.output) || result.output.endsWith(".txt") || result.output.endsWith(".json")) {
94
- result.outputType = result.output.endsWith(".txt") ? "txt" : result.output.endsWith(".json") ? "json" : result.output;
94
+ if (["txt", "json", "sarif", "none"].includes(result.output) ||
95
+ result.output.endsWith(".txt") ||
96
+ result.output.endsWith(".sarif") ||
97
+ result.output.endsWith(".json")) {
98
+ result.outputType = result.output.endsWith(".txt") ? "txt"
99
+ : result.output.endsWith(".json") ? "json"
100
+ : result.output.endsWith(".sarif") ? "sarif"
101
+ : result.output;
95
102
  result.codenarcArgs.push(`-report=json:stdout`);
96
103
  } else if (["html", "xml"].includes(result.output.split(".").pop())) {
97
104
  result.outputType = result.output
@@ -100,11 +107,11 @@ async function prepareCodeNarcCall(options) {
100
107
  .endsWith("html")
101
108
  ? "html"
102
109
  : result.output
103
- .split(".")
104
- .pop()
105
- .endsWith("xml")
106
- ? "xml"
107
- : "";
110
+ .split(".")
111
+ .pop()
112
+ .endsWith("xml")
113
+ ? "xml"
114
+ : "";
108
115
  const ext = result.output.split(".").pop();
109
116
  result.codenarcArgs.push(`-report=${ext}:${result.output}`);
110
117
 
@@ -265,9 +272,9 @@ async function parseCodeNarcResult(options, codeNarcBaseDir, codeNarcJsonResult,
265
272
  }
266
273
 
267
274
  // Parse error definitions & build url if not already done and not noreturnrules option
268
- if (result.rules == null && options.returnrules === true) {
275
+ if (result.rules == null && (options.returnrules === true || options.output.includes("sarif"))) {
269
276
  const configAllFileName = await getConfigFileName(__dirname, null, [".groovylintrc-all.json"]);
270
- const grooylintrcAllRules = Object.keys(JSON.parse(fse.readFileSync(configAllFileName, "utf8").toString()).rules);
277
+ const grooylintrcAllRules = Object.keys(JSON.parse(fs.readFileSync(configAllFileName, "utf8").toString()).rules);
271
278
  const rules = {};
272
279
  for (const ruleDef of codeNarcJsonResult.rules) {
273
280
  const ruleName = ruleDef.name;
@@ -295,7 +302,7 @@ async function buildRuleSets(options) {
295
302
  const normalizedRulesets = rulesetSplits.map(rulesetFile => {
296
303
  const fullFile = path.resolve(rulesetFile);
297
304
  // Encode file name so CodeNarc understands it
298
- if (fse.exists(fullFile)) {
305
+ if (fs.existsSync(fullFile)) {
299
306
  return "file:" + encodeURIComponent(fullFile);
300
307
  }
301
308
  // File name has already been encoded: let it as it is (will make CodeNarc fail if file not existing)
@@ -337,8 +344,8 @@ async function buildRuleSets(options) {
337
344
  typeof ruleFromConfig === "object"
338
345
  ? Object.assign(ruleFromConfig, ruleOptions)
339
346
  : Object.keys(ruleOptions).length > 0
340
- ? ruleOptions
341
- : ruleFromConfig;
347
+ ? ruleOptions
348
+ : ruleFromConfig;
342
349
  const ruleDef = buildCodeNarcRule(ruleName, mergedRuleConfig);
343
350
  return ruleDef;
344
351
  });
@@ -355,8 +362,8 @@ async function buildRuleSets(options) {
355
362
  typeof ruleDef === "object"
356
363
  ? Object.assign(ruleDef, ruleFromRuleSetsArg)
357
364
  : Object.keys(ruleFromRuleSetsArg).length > 0
358
- ? ruleFromRuleSetsArg
359
- : ruleDef;
365
+ ? ruleFromRuleSetsArg
366
+ : ruleDef;
360
367
  }
361
368
  // Add in the list of rules to test , except if it is disabled
362
369
  if (!(ruleDef === "off" || ruleDef.disabled === true || ruleDef.enabled === false)) {
@@ -415,7 +422,7 @@ function getCodeNarcPriorityCode(ruleFromConfig) {
415
422
  async function manageDeleteTmpFiles(tmpGroovyFileName) {
416
423
  // Remove temporary groovy file created for source argument if provided
417
424
  if (tmpGroovyFileName) {
418
- await fse.remove(tmpGroovyFileName);
425
+ await fs.remove(tmpGroovyFileName);
419
426
  debug(`Removed temp file ${tmpGroovyFileName} as it is not longer used`);
420
427
  tmpGroovyFileName = null;
421
428
  }
@@ -0,0 +1,56 @@
1
+ import groovy.io.FileType
2
+ import groovy.json.*
3
+ import groovy.time.TimeCategory
4
+ import static groovyx.gpars.GParsPool.withPool
5
+
6
+ def script = new GroovyScriptEngine( "." ).with{
7
+ loadScriptByName( 'Utils.groovy' ) ;
8
+ }
9
+ this.metaClass.mixin script
10
+
11
+ def returnCode = 0
12
+ Exception eThrow = null ;
13
+ try {
14
+ initialize(args) ;
15
+ } catch (Exception e){
16
+ eThrow = e ;
17
+ returnCode = 1
18
+ }
19
+ if (eThrow == null){
20
+ return 0 ;
21
+ }
22
+ else {
23
+ throw eThrow ;
24
+ return 1 ;
25
+ }
26
+
27
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
28
+ ///////////////////////////////////////////////// SCRIPT /////////////////////////////////////////////////
29
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
30
+ def initialize(args3) { //
31
+ def executor = new TestExecutor(args3)
32
+ return executor
33
+ }
34
+
35
+ class TestExecutor {
36
+
37
+ public TestExecutor( args2) {
38
+ this.testExternalGlobalProps()
39
+ }
40
+
41
+ public testExternalGlobalProps() {
42
+ Utils.printlnLog( '########## testExternalGlobalProps')
43
+ def globalKeyName = new Random().with { (1..9).collect { (('a'..'z')).join()[ nextInt((('a'..'z')).join().length())]}.join()}
44
+ Utils.printlnLog( "Generated random key: ${globalKeyName}")
45
+ Utils.setExternalValue(globalKeyName , 'lelama' , 'nul')
46
+ def storedValue = Utils.getExternalValue(globalKeyName , 'lelama')
47
+ assert storedValue == 'nul' , 'Error in global prop key storage/ retrieval (1)'
48
+ Utils.setExternalValue(globalKeyName , 'lelama2' , 'nul2')
49
+ def storedValue2 = Utils.getExternalValue(globalKeyName , 'lelama2')
50
+ assert storedValue2 == 'nul2' , 'Error in global prop key storage/ retrieval (2)'
51
+ def storedValueBack = Utils.getExternalValue(globalKeyName , 'lelama')
52
+ assert storedValueBack == 'nul' , 'Error in global prop key storage/ retrieval (3)'
53
+ Utils.printlnLog( Utils.getExternalValue(globalKeyName))
54
+ }
55
+
56
+ }
@@ -10,7 +10,7 @@ const { NPM_GROOVY_LINT_CONSTANTS, loadConfig, getConfigFileName } = require("./
10
10
  const optionsDefinition = require("./options");
11
11
  const { computeStats, processOutput } = require("./output.js");
12
12
  const { recordAnonymousEvent } = require("./analytics.js");
13
- const { getSourceLines, isErrorInLogLevelScope } = require("./utils");
13
+ const { getNpmGroovyLintVersion, getSourceLines, isErrorInLogLevelScope } = require("./utils");
14
14
 
15
15
  class NpmGroovyLint {
16
16
  "use strict";
@@ -179,16 +179,7 @@ class NpmGroovyLint {
179
179
 
180
180
  // Show version
181
181
  if (this.options.version) {
182
- let v = process.env.npm_package_version;
183
- if (!v) {
184
- try {
185
- const FindPackageJson = require("find-package-json");
186
- const finder = FindPackageJson(__dirname);
187
- v = finder.next().value.version;
188
- } catch {
189
- v = "error";
190
- }
191
- }
182
+ const v = getNpmGroovyLintVersion();
192
183
 
193
184
  const codeNarcVersionLinter = await new NpmGroovyLint([process.execPath, "", "--codenarcargs", "-version"], {}).run();
194
185
  const codeNarcVersionLines = [(await getSourceLines(codeNarcVersionLinter.codeNarcStdOut))[0]];
package/lib/options.js CHANGED
@@ -119,8 +119,8 @@ module.exports = optionator({
119
119
  alias: "o",
120
120
  type: "String",
121
121
  default: "txt",
122
- description: "Output format (txt,json,html,xml), or path to a file with one of these extensions",
123
- example: ["txt", "json", "./logs/myLintResults.txt", "./logs/myLintResults.json", "./logs/myLintResults.html", "./logs/myLintResults.xml"]
122
+ description: "Output format (txt,json,sarif,html,xml), or path to a file with one of these extensions",
123
+ example: ["txt", "json", "sarif", "./logs/myLintResults.txt", "./logs/myLintResults.json","./logs/myLintResults.sarif", "./logs/myLintResults.html", "./logs/myLintResults.xml"]
124
124
  },
125
125
  {
126
126
  option: "loglevel",
package/lib/output.js CHANGED
@@ -1,8 +1,9 @@
1
1
  // Output management
2
2
  const c = require("ansi-colors");
3
3
  const fse = require("fs-extra");
4
+ const { SarifBuilder, SarifRunBuilder, SarifResultBuilder, SarifRuleBuilder } = require("node-sarif-builder");
4
5
  const path = require("path");
5
- const { isErrorInLogLevelScope } = require("./utils");
6
+ const { isErrorInLogLevelScope, getNpmGroovyLintVersion } = require("./utils");
6
7
 
7
8
  // Compute statistics for output
8
9
  function computeStats(lintResult) {
@@ -204,8 +205,81 @@ async function processOutput(outputType, output, lintResult, options, fixer = nu
204
205
  outputString = JSON.stringify(lintResult);
205
206
  console.log(outputString);
206
207
  }
208
+ } else if (outputType === "sarif") {
209
+ const sarifJsonString = buildSarifResult(lintResult);
210
+ // SARIF file
211
+ if (output.endsWith(".sarif")) {
212
+ fse.writeFileSync(output, sarifJsonString);
213
+ const absolutePath = path.resolve(".", output);
214
+ outputString = "GroovyLint: Logged SARIF results in file " + absolutePath;
215
+ console.info(outputString);
216
+ } else {
217
+ // SARIF in stdout
218
+ outputString = sarifJsonString;
219
+ console.log(sarifJsonString);
220
+ }
207
221
  }
208
222
  return outputString;
209
223
  }
210
224
 
225
+ function buildSarifResult(lintResult) {
226
+ // SARIF builder
227
+ const sarifBuilder = new SarifBuilder();
228
+ // SARIF Run builder
229
+ const sarifRunBuilder = new SarifRunBuilder().initSimple({
230
+ toolDriverName: "npm-groovy-lint",
231
+ toolDriverVersion: getNpmGroovyLintVersion(),
232
+ url: "https://nvuillam.github.io/npm-groovy-lint/"
233
+ });
234
+ // SARIF rules
235
+ for (const ruleId of Object.keys(lintResult.rules || {})) {
236
+ const rule = lintResult.rules[ruleId];
237
+ const sarifRuleBuilder = new SarifRuleBuilder().initSimple({
238
+ ruleId: ruleId,
239
+ shortDescriptionText: rule.description,
240
+ helpUri: rule.docUrl
241
+ });
242
+ sarifRunBuilder.addRule(sarifRuleBuilder);
243
+ }
244
+ // Add SARIF results (individual errors)
245
+ for (const fileNm of Object.keys(lintResult.files)) {
246
+ const fileErrors = lintResult.files[fileNm].errors;
247
+ for (const err of fileErrors) {
248
+ const sarifResultBuilder = new SarifResultBuilder();
249
+ const sarifResultInit = {
250
+ level: err.severity === "info" ? "note" : err.severity, // Other values can be "warning" or "error"
251
+ messageText: err.msg,
252
+ ruleId: err.rule,
253
+ fileUri: process.env.SARIF_URI_ABSOLUTE
254
+ ? "file:///" + fileNm.replace(/\\/g, "/")
255
+ : path.relative(process.cwd(), fileNm)
256
+ };
257
+ if (err && err.range && err.range.start && (err.range.start.line === 0 || err.range.start.line > 0)) {
258
+ sarifResultInit.startLine = fixLine(err.range.start.line);
259
+ sarifResultInit.startColumn = fixCol(err.range.start.character);
260
+ sarifResultInit.endLine = fixLine(err.range.end.line);
261
+ sarifResultInit.endColumn = fixCol(err.range.end.character);
262
+ }
263
+ sarifResultBuilder.initSimple(sarifResultInit);
264
+ sarifRunBuilder.addResult(sarifResultBuilder);
265
+ }
266
+ }
267
+ sarifBuilder.addRun(sarifRunBuilder);
268
+ return sarifBuilder.buildSarifJsonString({ indent: false });
269
+ }
270
+
271
+ function fixLine(val) {
272
+ if (val === null) {
273
+ return undefined;
274
+ }
275
+ return val === 0 ? 1 : val;
276
+ }
277
+
278
+ function fixCol(val) {
279
+ if (val === null) {
280
+ return undefined;
281
+ }
282
+ return val === 0 ? 1 : val + 1;
283
+ }
284
+
211
285
  module.exports = { computeStats, processOutput };
@@ -0,0 +1,21 @@
1
+ // Too many methods in a class
2
+
3
+ const { getVariableRange } = require("../utils");
4
+
5
+ const rule = {
6
+ variables: [
7
+ {
8
+ name: "METHODNAME",
9
+ regex: /Method "(.*)" has a dynamic return type/,
10
+ regexPos: 1
11
+ }
12
+ ],
13
+ range: {
14
+ type: "function",
15
+ func: (errLine, errItem, evaluatedVars) => {
16
+ return getVariableRange(errLine, evaluatedVars, "METHODNAME", errItem);
17
+ }
18
+ }
19
+ };
20
+
21
+ module.exports = { rule };
@@ -1,6 +1,6 @@
1
1
  // Unused import
2
2
 
3
- const { getVariable } = require("../utils");
3
+ const { getVariable, getVariableRange } = require("../utils");
4
4
 
5
5
  const rule = {
6
6
  scope: "file",
@@ -12,6 +12,12 @@ const rule = {
12
12
  regexPos: 1
13
13
  }
14
14
  ],
15
+ range: {
16
+ type: "function",
17
+ func: (errLine, errItem, evaluatedVars) => {
18
+ return getVariableRange(errLine, evaluatedVars, "CLASSNAME", errItem);
19
+ }
20
+ },
15
21
  fix: {
16
22
  label: "Remove unused import",
17
23
  type: "function",
package/lib/utils.js CHANGED
@@ -394,6 +394,20 @@ function splitMulti(str, tokens) {
394
394
  return str;
395
395
  }
396
396
 
397
+ function getNpmGroovyLintVersion(){
398
+ let v = process.env.npm_package_version;
399
+ if (!v) {
400
+ try {
401
+ const FindPackageJson = require("find-package-json");
402
+ const finder = FindPackageJson(__dirname);
403
+ v = finder.next().value.version;
404
+ } catch {
405
+ v = "error";
406
+ }
407
+ }
408
+ return v ;
409
+ }
410
+
397
411
  module.exports = {
398
412
  addImport,
399
413
  addSpaceAfterChar,
@@ -405,6 +419,7 @@ module.exports = {
405
419
  findRangeBetweenStrings,
406
420
  getIndentLength,
407
421
  getLastStringRange,
422
+ getNpmGroovyLintVersion,
408
423
  getOutOfBracesStrings,
409
424
  getSourceLines,
410
425
  getStringRange,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "npm-groovy-lint",
3
- "version": "9.3.0",
3
+ "version": "9.4.1",
4
4
  "description": "Lint, format and auto-fix your Groovy / Jenkinsfile / Gradle files",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -48,7 +48,7 @@
48
48
  "ansi-colors": "^4.1.1",
49
49
  "axios": "^0.21.1",
50
50
  "chalk": "^4.1.2",
51
- "cli-progress": "^3.6.0",
51
+ "cli-progress": "^3.10.0",
52
52
  "debug": "^4.1.1",
53
53
  "decode-html": "^2.0.0",
54
54
  "find-java-home": "^1.1.0",
@@ -58,6 +58,7 @@
58
58
  "import-fresh": "^3.2.1",
59
59
  "ip": "^1.1.5",
60
60
  "java-caller": "^2.2.4",
61
+ "node-sarif-builder": "^2.0.1",
61
62
  "optionator": "^0.8.3",
62
63
  "semver": "^7.1.3",
63
64
  "strip-json-comments": "^3.0.1",
@@ -75,7 +76,7 @@
75
76
  "eslint-plugin-standard": "^5.0.0",
76
77
  "mocha": "^7.0.1",
77
78
  "nyc": "^15.1.0",
78
- "prettier": "1.19.1",
79
+ "prettier": "^1.19.1",
79
80
  "rimraf": "^3.0.2",
80
81
  "which": "^2.0.2"
81
82
  },