Repository: stoplightio/spectral
Pull request #1699 (2021-06-29 07:16:59)

feat(cli): demand some ruleset to be present
Related to #1698 

**Checklist**

- [x] Tests added / updated
- [ ] Docs added / updated

**Does this PR introduce a breaking change?**

- [x] Yes
- [ ] No


**Additional context**

Bear in mind that this only relevant to the situation where you do not provide a ruleset and have no `.spectral.{js,json,yaml,yml}` at the same time.
We no longer load any default ruleset, thus it's on user now to specify some.


wmhilton: Thanks!

Patch:
diff --git a/packages/cli/src/services/linter/utils/getRuleset.ts b/packages/cli/src/services/linter/utils/getRuleset.ts
index 3a7dbcf7..b2e4e6b3 100644
--- a/packages/cli/src/services/linter/utils/getRuleset.ts
+++ b/packages/cli/src/services/linter/utils/getRuleset.ts
@@ -28,7 +28,9 @@ export async function getRuleset(rulesetFile: Optional<string>): Promise<Ruleset
   }
 
   if (rulesetFile === void 0) {
-    return new Ruleset({ rules: {} });
+    throw new Error(
+      'No ruleset has been found. Please provide a ruleset using the --ruleset CLI argument, or make sure your ruleset file matches .?spectral.(js|ya?ml|json)',
+    );
   }
 
   let ruleset;
diff --git a/packages/core/src/spectral.ts b/packages/core/src/spectral.ts
index 28631c42..90c3bf72 100644
--- a/packages/core/src/spectral.ts
+++ b/packages/core/src/spectral.ts
@@ -22,7 +22,7 @@ export * from './types';
 export class Spectral {
   private readonly _resolver: Resolver;
 
-  public ruleset: Ruleset = new Ruleset({ rules: {} });
+  public ruleset?: Ruleset;
 
   protected readonly runtime: RunnerRuntime;
 
@@ -55,6 +55,10 @@ export class Spectral {
     target: IParsedResult | IDocument | Record<string, unknown> | string,
     opts: IRunOpts = {},
   ): Promise<ISpectralFullResult> {
+    if (this.ruleset === void 0) {
+      throw new Error('No ruleset has been defined. Have you called setRuleset()?');
+    }
+
     const document = this.parseDocument(target);
     const ruleset = this.ruleset.fromSource(document.source);
 


Test patch:
diff --git a/packages/cli/src/services/__tests__/linter.test.ts b/packages/cli/src/services/__tests__/linter.test.ts
index 17c7d815..e3e63230 100644
--- a/packages/cli/src/services/__tests__/linter.test.ts
+++ b/packages/cli/src/services/__tests__/linter.test.ts
@@ -30,18 +30,22 @@ async function run(command: string) {
 
 describe('Linter service', () => {
   let consoleLogSpy: jest.SpyInstance;
+  let consoleErrorSpy: jest.SpyInstance;
   let processCwdSpy: jest.SpyInstance;
 
   beforeEach(() => {
-    consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {
-      // no-op
-    });
+    const noop = () => {
+      /* no-op */
+    };
+    consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(noop);
+    consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(noop);
 
     processCwdSpy = jest.spyOn(process, 'cwd').mockReturnValue(join(__dirname, '__fixtures__'));
   });
 
   afterEach(() => {
     consoleLogSpy.mockRestore();
+    consoleErrorSpy.mockRestore();
     processCwdSpy.mockRestore();
 
     nock.cleanAll();
@@ -88,6 +92,13 @@ describe('Linter service', () => {
     ]);
   });
 
+  it('demands some ruleset to be present', () => {
+    processCwdSpy.mockReturnValue(join(__dirname, '__fixtures__/resolver'));
+    return expect(run(`lint stoplight-info-document.json`)).rejects.toThrow(
+      'No ruleset has been found. Please provide a ruleset using the --ruleset CLI argument, or make sure your ruleset file matches .?spectral.(js|ya?ml|json)',
+    );
+  });
+
   describe('when document is local file', () => {
     describe('and the file is expected to have no warnings', () => {
       it('outputs no issues', () => {
diff --git a/packages/core/src/__tests__/linter.test.ts b/packages/core/src/__tests__/linter.test.ts
index dbaa26a8..1e0d4ddc 100644
--- a/packages/core/src/__tests__/linter.test.ts
+++ b/packages/core/src/__tests__/linter.test.ts
@@ -30,6 +30,12 @@ describe('linter', () => {
     spectral = new Spectral();
   });
 
+  test('should demand some result', () => {
+    return expect(spectral.run(new Document('123', Parsers.Json))).rejects.toThrow(
+      'No ruleset has been defined. Have you called setRuleset()?',
+    );
+  });
+
   test('should not throw if passed in value is not an object', () => {
     spectral.setRuleset({
       rules: {
@@ -511,6 +517,7 @@ responses:: !!foo
      description: c
 `;
 
+    spectral.setRuleset({ rules: {} });
     const result = await spectral.run(responses, { ignoreUnknownFormat: true });
 
     expect(result).toEqual(
@@ -599,6 +606,7 @@ responses:: !!foo
   });
 
   test('should report invalid schema $refs', async () => {
+    spectral.setRuleset({ rules: {} });
     const result = await spectral.run(
       JSON.stringify(
         {
@@ -636,6 +644,8 @@ responses:: !!foo
 
   test('should report when a resolver is no t defined for a given $ref type', async () => {
     const s = new Spectral({ resolver: new Resolver() });
+    s.setRuleset(new Ruleset({ rules: {} }));
+
     const document = JSON.stringify({
       'file-refs': [{ $ref: './models/pet.yaml' }, { $ref: '../common/models/error.yaml' }],
     });
@@ -660,6 +670,7 @@ responses:: !!foo
 
   describe('reports duplicated properties for', () => {
     test('JSON format', async () => {
+      spectral.setRuleset({ rules: {} });
       const result = await spectral.run('{"foo":true,"foo":false}', {
         ignoreUnknownFormat: true,
       });
@@ -685,6 +696,7 @@ responses:: !!foo
     });
 
     test('YAML format', async () => {
+      spectral.setRuleset({ rules: {} });
       const result = await spectral.run(`foo: bar\nfoo: baz`, {
         ignoreUnknownFormat: true,
       });
@@ -711,6 +723,7 @@ responses:: !!foo
   });
 
   test('should report invalid YAML mapping keys', async () => {
+    spectral.setRuleset({ rules: {} });
     const results = await spectral.run(
       `responses:
   200:
@@ -1221,6 +1234,7 @@ responses:: !!foo
 
   test.each(['1', 'null', '', 'false'])('given %s input, should report nothing', async input => {
     const s = new Spectral();
+    s.setRuleset(new Ruleset({ rules: {} }));
 
     const source = '/tmp/file.yaml';
     const doc = new Document(input, Parsers.Yaml, source);
diff --git a/packages/core/src/__tests__/spectral.test.ts b/packages/core/src/__tests__/spectral.test.ts
index 377082e2..ddd35891 100644
--- a/packages/core/src/__tests__/spectral.test.ts
+++ b/packages/core/src/__tests__/spectral.test.ts
@@ -4,6 +4,7 @@ import * as Parsers from '@stoplight/spectral-parsers';
 import { Resolver } from '@stoplight/spectral-ref-resolver';
 import { Document } from '../document';
 import { Spectral } from '../spectral';
+import { Ruleset } from '../ruleset';
 
 describe('spectral', () => {
   describe('when a $ref appears', () => {
@@ -19,6 +20,7 @@ describe('spectral', () => {
 
         const target = { foo: 'bar' };
 
+        s.setRuleset(new Ruleset({ rules: {} }));
         await s.run(target);
 
         expect(resolve).toBeCalledWith(target, {
diff --git a/test-harness/scenarios/fail-on-unmatched-glob-patterns.scenario b/test-harness/scenarios/fail-on-unmatched-glob-patterns.scenario
index 37c84ce8..77987218 100644
--- a/test-harness/scenarios/fail-on-unmatched-glob-patterns.scenario
+++ b/test-harness/scenarios/fail-on-unmatched-glob-patterns.scenario
@@ -1,7 +1,11 @@
 ====test====
 Reports unmatched glob patterns
+====asset:ruleset====
+module.exports = {
+  rules: {}
+}
 ====command====
-{bin} lint ./*oops.{yml,yaml,json} --fail-on-unmatched-globs
+{bin} lint ./*oops.{yml,yaml,json} --fail-on-unmatched-globs --ruleset {asset:ruleset}
 ====status====
 2
 ====stderr====
diff --git a/test-harness/scenarios/no-ruleset.scenario b/test-harness/scenarios/no-ruleset.scenario
new file mode 100644
index 00000000..b96fcb64
--- /dev/null
+++ b/test-harness/scenarios/no-ruleset.scenario
@@ -0,0 +1,10 @@
+====test====
+Ruleset is required for Spectral run linting
+====document====
+type: string
+====command====
+{bin} lint {document} --ignore-unknown-format
+====status====
+2
+====stderr====
+No ruleset has been found. Please provide a ruleset using the --ruleset CLI argument, or make sure your ruleset file matches .?spectral.(js|ya?ml|json)
diff --git a/test-harness/scenarios/proxy-agent.scenario b/test-harness/scenarios/proxy-agent.scenario
index 0d93ce74..e3ce1003 100644
--- a/test-harness/scenarios/proxy-agent.scenario
+++ b/test-harness/scenarios/proxy-agent.scenario
@@ -5,8 +5,12 @@ foo:
   $ref: http://localhost:3002/foo.json#/ref
 ====env====
 PROXY=http://localhost:3001
+====asset:ruleset====
+module.exports = {
+  rules: {}
+}
 ====command====
-{bin} lint {document} --ignore-unknown-format
+{bin} lint {document} --ignore-unknown-format --ruleset {asset:ruleset}
 ====stdout====
 {document}
  2:9  error  invalid-ref  FetchError: request to http://localhost:3002/foo.json failed, reason: connect ECONNREFUSED 127.0.0.1:3001  foo.$ref