Repository: stoplightio/spectral
Pull request #562 (2019-09-17 23:44:01)

fix(resolving): persist parseMap until resolver cache instance is gone
**Checklist**

- [x] Tests added / updated
- [x] Docs added / updated

**Does this PR introduce a breaking change?**

- [ ] Yes
- [x] No

It's needed by Studio.

https://github.com/stoplightio/spectral/commit/5790e05486b5bae0fc42f05b2ece44371e21cee5 fixes random failures on CI. I forgot to mock the request.


Patch:
diff --git a/src/spectral.ts b/src/spectral.ts
index 80b56daf..c521711f 100644
--- a/src/spectral.ts
+++ b/src/spectral.ts
@@ -4,8 +4,8 @@ import {
   parseWithPointers as parseJSONWithPointers,
   safeStringify,
 } from '@stoplight/json';
-import { Cache, Resolver } from '@stoplight/json-ref-resolver';
-import { IUriParser } from '@stoplight/json-ref-resolver/types';
+import { Resolver } from '@stoplight/json-ref-resolver';
+import { ICache, IUriParser } from '@stoplight/json-ref-resolver/types';
 import { extname } from '@stoplight/path';
 import { Dictionary } from '@stoplight/types';
 import {
@@ -43,8 +43,9 @@ import { IRuleset } from './types/ruleset';
 export * from './types';
 
 export class Spectral {
-  private _resolver: Resolver;
-  private _uriCache: Cache;
+  private readonly _resolver: Resolver;
+  private readonly _parsedMap: IParseMap;
+  private static readonly _parsedCache = new WeakMap<ICache, IParseMap>();
   public functions: FunctionCollection = { ...defaultFunctions };
   public rules: RunRuleCollection = {};
 
@@ -53,7 +54,19 @@ export class Spectral {
   constructor(opts?: IConstructorOpts) {
     this._resolver = opts && opts.resolver ? opts.resolver : new Resolver();
     this.formats = {};
-    this._uriCache = new Cache();
+
+    const _parsedMap = Spectral._parsedCache.get(this._resolver.uriCache);
+    if (_parsedMap) {
+      this._parsedMap = _parsedMap;
+    } else {
+      this._parsedMap = {
+        refs: {},
+        parsed: {},
+        pointers: {},
+      };
+
+      Spectral._parsedCache.set(this._resolver.uriCache, this._parsedMap);
+    }
   }
 
   public async runWithResolved(
@@ -83,7 +96,6 @@ export class Spectral {
     const resolved = new Resolved(
       parsedResult,
       await this._resolver.resolve(parsedResult.parsed.data, {
-        uriCache: this._uriCache,
         baseUri: documentUri,
         parseResolveResult: this._parseResolveResult(refDiagnostics),
       }),
@@ -202,12 +214,6 @@ export class Spectral {
     this.formats[format] = fn;
   }
 
-  private _parsedMap: IParseMap = {
-    refs: {},
-    parsed: {},
-    pointers: {},
-  };
-
   private _processExternalRef(parsedResult: IParsedResult, opts: IUriParser) {
     const ref = opts.targetAuthority.toString();
     this._parsedMap.parsed[ref] = parsedResult;


Test patch:
diff --git a/src/__tests__/spectral.test.ts b/src/__tests__/spectral.test.ts
index c2ddb094..20bd1e35 100644
--- a/src/__tests__/spectral.test.ts
+++ b/src/__tests__/spectral.test.ts
@@ -1,4 +1,3 @@
-import { Cache } from '@stoplight/json-ref-resolver';
 import { DiagnosticSeverity, Dictionary } from '@stoplight/types';
 import { isParsedResult, Spectral } from '../spectral';
 import { IParsedResult, RuleFunction } from '../types';
@@ -127,6 +126,11 @@ describe('spectral', () => {
   describe('when a $ref appears', () => {
     test('will call the resolver with target', async () => {
       const fakeResolver = {
+        uriCache: {
+          get: jest.fn(),
+          set: jest.fn(),
+          has: jest.fn(),
+        },
         resolve: jest.fn(() => Promise.resolve([])),
       };
 
@@ -140,7 +144,6 @@ describe('spectral', () => {
 
       expect(fakeResolver.resolve).toBeCalledWith(target, {
         authority: undefined,
-        uriCache: expect.any(Cache),
         parseResolveResult: expect.any(Function),
       });
     });
diff --git a/src/rulesets/__tests__/reader.jest.test.ts b/src/rulesets/__tests__/reader.jest.test.ts
index 603dd0e0..ebb724e7 100644
--- a/src/rulesets/__tests__/reader.jest.test.ts
+++ b/src/rulesets/__tests__/reader.jest.test.ts
@@ -485,6 +485,10 @@ describe('Rulesets reader', () => {
   });
 
   it('should not fail if function cannot be loaded', () => {
+    nock('https://unpkg.com')
+      .get('/boo.js')
+      .reply(404);
+
     return expect(readRuleset(rulesetWithMissingFunctions)).resolves.toEqual({
       rules: {},
       functions: {},