Repository: stoplightio/spectral
Pull request #1371 (2020-10-01 11:56:29)

feat(cli): introduce --fail-on-unmatched-globs flag
Closes https://github.com/stoplightio/spectral/issues/1117

**Checklist**

- [x] Tests added / updated
- [x] Docs added / updated

**Does this PR introduce a breaking change?**

- [ ] Yes
- [x] No



philsturgeon: Ok let’s do things one step at a time. Deprecate old flag and introduce this flag, then remove the old way in the next major, then flip new behavior default on in the major after that. OSS! 😅

--
Phil Sturgeon
DevRel @ Stoplight.io

> On Oct 6, 2020, at 12:55, Jakub Rożek <notifications@github.com> wrote:
> 
> ﻿
> @P0lip commented on this pull request.
> 
> In docs/guides/2-cli.md:
> 
> > @@ -35,6 +35,7 @@ Other options include:
>                                                                             [boolean] [default: false]
>    --ignore-unknown-format      do not warn about unmatched formats         [boolean] [default: false]
>    --show-unmatched-globs       show unmatched glob patterns                [boolean] [default: false]
> +  --fail-on-unmatched-globs    fail on unmatched glob patterns             [boolean] [default: false]
> Should we deprecate show-unmatched-globs and have this be the new "more better" way of doing it?
> 
> Yeah, that'd be a good idea.
> By the way, ESLint exits with non-zero code by default if any of the provided glob patterns is not matched.
> Example:
> 
> $ eslint --cache --cache-location .cache/ --ext=.js,.ts src test-harness b
> 
> Oops! Something went wrong! :(
> 
> ESLint: 6.8.0.
> 
> No files matching the pattern "b" were found.
> Please check for typing mistakes in the pattern.
> 
> error Command failed with exit code 2.
> IMHO Spectral could do the same.
> 
> —
> You are receiving this because your review was requested.
> Reply to this email directly, view it on GitHub, or unsubscribe.


Patch:
diff --git a/docs/guides/2-cli.md b/docs/guides/2-cli.md
index e85d462c..0b11d251 100644
--- a/docs/guides/2-cli.md
+++ b/docs/guides/2-cli.md
@@ -35,7 +35,9 @@ Other options include:
   --display-only-failures, -D  only output results equal to or greater than --fail-severity
                                                                            [boolean] [default: false]
   --ignore-unknown-format      do not warn about unmatched formats         [boolean] [default: false]
-  --show-unmatched-globs       show unmatched glob patterns                [boolean] [default: false]
+  --show-unmatched-globs       show unmatched glob patterns
+                               [deprecated: use --fail-on-unmatched-globs] [boolean] [default: false]
+  --fail-on-unmatched-globs    fail on unmatched glob patterns             [boolean] [default: false]
   --verbose, -v                increase verbosity                                           [boolean]
   --quiet, -q                  no logging - output only                                     [boolean]
 ```
diff --git a/package.json b/package.json
index 80d6262a..7f53016f 100644
--- a/package.json
+++ b/package.json
@@ -77,7 +77,7 @@
     "chalk": "4.0.0",
     "eol": "0.9.1",
     "expression-eval": "3.1.2",
-    "fast-glob": "3.1.0",
+    "fast-glob": "3.2.4",
     "jsonpath-plus": "4.0.0",
     "lodash": "4.17.19",
     "nanoid": "2.1.11",
diff --git a/src/cli/commands/lint.ts b/src/cli/commands/lint.ts
index 8e8994b2..f43fb8b5 100644
--- a/src/cli/commands/lint.ts
+++ b/src/cli/commands/lint.ts
@@ -109,6 +109,12 @@ const lintCommand: CommandModule = {
           description: 'show unmatched glob patterns',
           type: 'boolean',
           default: false,
+          deprecated: 'use --fail-on-unmatched-globs',
+        },
+        'fail-on-unmatched-globs': {
+          description: 'fail on unmatched glob patterns',
+          type: 'boolean',
+          default: false,
         },
         verbose: {
           alias: 'v',
@@ -133,6 +139,7 @@ const lintCommand: CommandModule = {
       encoding,
       ignoreUnknownFormat,
       showUnmatchedGlobs,
+      failOnUnmatchedGlobs,
       ...config
     } = (args as unknown) as ILintConfig & {
       documents: Array<number | string>;
@@ -145,6 +152,7 @@ const lintCommand: CommandModule = {
       output,
       encoding,
       ignoreUnknownFormat,
+      failOnUnmatchedGlobs,
       showUnmatchedGlobs,
       ruleset,
       ...pick<Partial<ILintConfig>, keyof ILintConfig>(config, ['skipRule', 'verbose', 'quiet', 'resolver']),
@@ -156,7 +164,7 @@ const lintCommand: CommandModule = {
         return results;
       })
       .then(results => {
-        if (results.length) {
+        if (results.length > 0) {
           process.exitCode = severeEnoughToFail(results, failSeverity) ? 1 : 0;
         } else if (!config.quiet) {
           console.log(`No results with a severity of '${failSeverity}' or higher found!`);
diff --git a/src/cli/services/linter/linter.ts b/src/cli/services/linter/linter.ts
index f538cbe7..163cd2b9 100644
--- a/src/cli/services/linter/linter.ts
+++ b/src/cli/services/linter/linter.ts
@@ -7,7 +7,7 @@ import { ILintConfig } from '../../../types/config';
 import { getRuleset, listFiles, skipRules } from './utils';
 import { getResolver } from './utils/getResolver';
 
-export async function lint(documents: Array<number | string>, flags: ILintConfig) {
+export async function lint(documents: Array<number | string>, flags: ILintConfig): Promise<IRuleResult[]> {
   const spectral = new Spectral({
     resolver: getResolver(flags.resolver),
   });
@@ -42,11 +42,20 @@ export async function lint(documents: Array<number | string>, flags: ILintConfig
     spectral.setRules(skipRules(ruleset.rules, flags));
   }
 
-  const [targetUris, unmatchedPatterns] = await listFiles(documents, !flags.showUnmatchedGlobs);
+  const [targetUris, unmatchedPatterns] = await listFiles(
+    documents,
+    !(flags.showUnmatchedGlobs || flags.failOnUnmatchedGlobs),
+  );
   const results: IRuleResult[] = [];
 
-  for (const unmatchedPattern of unmatchedPatterns) {
-    console.log(`Glob pattern \`${unmatchedPattern}\` did not match any files`);
+  if (unmatchedPatterns.length > 0) {
+    if (flags.failOnUnmatchedGlobs) {
+      throw new Error(`Unmatched glob patterns: \`${unmatchedPatterns.join(',')}\``);
+    }
+
+    for (const unmatchedPattern of unmatchedPatterns) {
+      console.log(`Glob pattern \`${unmatchedPattern}\` did not match any files`);
+    }
   }
 
   for (const targetUri of targetUris) {
diff --git a/src/types/config.ts b/src/types/config.ts
index f5f9eab3..2d8f0606 100644
--- a/src/types/config.ts
+++ b/src/types/config.ts
@@ -20,6 +20,7 @@ export interface ILintConfig {
   skipRule?: string[];
   ignoreUnknownFormat: boolean;
   showUnmatchedGlobs: boolean;
+  failOnUnmatchedGlobs: boolean;
   verbose?: boolean;
   quiet?: boolean;
 }
diff --git a/yarn.lock b/yarn.lock
index e78ffe89..2c3ec54b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2859,16 +2859,17 @@ fast-diff@^1.1.2:
   resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
   integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
 
-fast-glob@3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.1.0.tgz#77375a7e3e6f6fc9b18f061cddd28b8d1eec75ae"
-  integrity sha512-TrUz3THiq2Vy3bjfQUB2wNyPdGBeGmdjbzzBLhfHN4YFurYptCKwGq/TfiRavbGywFRzY6U2CdmQ1zmsY5yYaw==
+fast-glob@3.2.4:
+  version "3.2.4"
+  resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3"
+  integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==
   dependencies:
     "@nodelib/fs.stat" "^2.0.2"
     "@nodelib/fs.walk" "^1.2.3"
     glob-parent "^5.1.0"
     merge2 "^1.3.0"
     micromatch "^4.0.2"
+    picomatch "^2.2.1"
 
 fast-glob@^3.1.1:
   version "3.2.2"


Test patch:
diff --git a/src/cli/commands/__tests__/lint.test.ts b/src/cli/commands/__tests__/lint.test.ts
index 9693a3c1..d778f8e1 100644
--- a/src/cli/commands/__tests__/lint.test.ts
+++ b/src/cli/commands/__tests__/lint.test.ts
@@ -78,6 +78,7 @@ describe('lint', () => {
         format: 'stylish',
         ignoreUnknownFormat: false,
         showUnmatchedGlobs: false,
+        failOnUnmatchedGlobs: false,
       });
     });
   });
@@ -95,6 +96,7 @@ describe('lint', () => {
       format: 'stylish',
       ignoreUnknownFormat: false,
       showUnmatchedGlobs: false,
+      failOnUnmatchedGlobs: false,
     });
   });
 
@@ -106,6 +108,7 @@ describe('lint', () => {
       format: 'stylish',
       ignoreUnknownFormat: false,
       showUnmatchedGlobs: false,
+      failOnUnmatchedGlobs: false,
     });
   });
 
@@ -117,6 +120,7 @@ describe('lint', () => {
       format: 'json',
       ignoreUnknownFormat: false,
       showUnmatchedGlobs: false,
+      failOnUnmatchedGlobs: false,
     });
   });
 
@@ -175,6 +179,7 @@ describe('lint', () => {
       format: 'stylish',
       ignoreUnknownFormat: false,
       showUnmatchedGlobs: false,
+      failOnUnmatchedGlobs: false,
     });
   });
 
@@ -185,6 +190,7 @@ describe('lint', () => {
       format: 'stylish',
       ignoreUnknownFormat: true,
       showUnmatchedGlobs: false,
+      failOnUnmatchedGlobs: false,
     });
   });
 
@@ -195,6 +201,18 @@ describe('lint', () => {
       format: 'stylish',
       ignoreUnknownFormat: false,
       showUnmatchedGlobs: true,
+      failOnUnmatchedGlobs: false,
+    });
+  });
+
+  it('passes fail-on-unmatched-globs to lint', async () => {
+    await run('lint --fail-on-unmatched-globs ./__fixtures__/empty-oas2-document.json');
+    expect(lint).toHaveBeenCalledWith([expect.any(String)], {
+      encoding: 'utf8',
+      format: 'stylish',
+      ignoreUnknownFormat: false,
+      showUnmatchedGlobs: false,
+      failOnUnmatchedGlobs: true,
     });
   });
 
diff --git a/test-harness/scenarios/fail-on-unmatched-glob-patterns.scenario b/test-harness/scenarios/fail-on-unmatched-glob-patterns.scenario
new file mode 100644
index 00000000..37c84ce8
--- /dev/null
+++ b/test-harness/scenarios/fail-on-unmatched-glob-patterns.scenario
@@ -0,0 +1,8 @@
+====test====
+Reports unmatched glob patterns
+====command====
+{bin} lint ./*oops.{yml,yaml,json} --fail-on-unmatched-globs
+====status====
+2
+====stderr====
+Unmatched glob patterns: `./*oops.{yml,yaml,json}`
diff --git a/test-harness/scenarios/help-no-document.scenario b/test-harness/scenarios/help-no-document.scenario
index a38612dc..d167a570 100644
--- a/test-harness/scenarios/help-no-document.scenario
+++ b/test-harness/scenarios/help-no-document.scenario
@@ -22,6 +22,7 @@ Options:
   --fail-severity, -F          results of this level or above will trigger a failure exit code  [string] [choices: "error", "warn", "info", "hint"] [default: "error"]
   --display-only-failures, -D  only output results equal to or greater than --fail-severity  [boolean] [default: false]
   --ignore-unknown-format      do not warn about unmatched formats  [boolean] [default: false]
-  --show-unmatched-globs       show unmatched glob patterns  [boolean] [default: false]
+  --show-unmatched-globs       show unmatched glob patterns  [deprecated: use --fail-on-unmatched-globs] [boolean] [default: false]
+  --fail-on-unmatched-globs    fail on unmatched glob patterns  [boolean] [default: false]
   --verbose, -v                increase verbosity  [boolean]
   --quiet, -q                  no logging - output only  [boolean]