diff --git a/package.json b/package.json index ee2f306a6..eeb15e951 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@stoplight/spectral", "version": "0.0.0", - "description": "A flexible object linter with out of the box OpenAPI v2 and v3 support.", + "description": "A flexible object linter with out of the box support for OpenAPI v2 and v3.", "keywords": [ "json linter", "linter", @@ -50,10 +50,10 @@ "should": "13.2.x" }, "devDependencies": { - "@stoplight/scripts": "3.0.2", + "@stoplight/scripts": "3.1.0", "@types/jsonpath": "0.2.x", "@types/lodash": "4.x.x", - "typescript": "3.2.1" + "typescript": "3.2.2" }, "lint-staged": { "*.{ts,tsx}$": [ diff --git a/src/__tests__/functions.ts b/src/__tests__/functions.ts new file mode 100644 index 000000000..773ef1a68 --- /dev/null +++ b/src/__tests__/functions.ts @@ -0,0 +1,617 @@ +import { Spectral } from '../spectral'; +import { IRuleResult, Rule, RuleFunction } from '../types'; + +const applyRuleToObject = (r: Rule, o: object): IRuleResult[] => { + const s = new Spectral(); + s.addRules({ + testRule: r, + }); + return s.run(o).results; +}; + +describe('functions', () => { + describe('truthy', () => { + test('returns result if value is not present', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$.info', + then: { + function: RuleFunction.TRUTHY, + functionOptions: { properties: 'something-not-present' }, + }, + }, + { + info: { + version: '1.0.0', + }, + } + ).length + ).toEqual(1); + }); + + test('returns result if value is not truthy', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$', + then: { + function: RuleFunction.TRUTHY, + functionOptions: { properties: 'count' }, + }, + }, + { + count: 0, + } + ).length + ).toEqual(1); + }); + + test('returns results for multiple properties', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$', + then: { + function: RuleFunction.TRUTHY, + functionOptions: { properties: ['count', 'name', 'count2'] }, + }, + }, + { + count: 0, + name: 'joe', + count2: 0, + } + ).length + ).toEqual(2); + }); + + test('doesnt return result if value is present', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$', + then: { + function: RuleFunction.TRUTHY, + functionOptions: { properties: 'info' }, + }, + }, + { + info: { + version: '1.0.0', + }, + } + ).length + ).toEqual(0); + }); + }); + + describe('alphabetical', () => { + describe('arrays', () => { + test('returns result if value keys are not alphabetized', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$.info', + then: { + field: 'tags', + function: RuleFunction.ALPHABETICAL, + functionOptions: { keyedBy: 'name' }, + }, + }, + { + info: { + tags: [{ name: 'Far', description: 'bar' }, { name: 'Boo', description: 'foo' }], + }, + } + ).length + ).toEqual(1); + }); + + test('dont return result if value keys are alphabetized', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$.info.tags', + then: { + function: RuleFunction.ALPHABETICAL, + functionOptions: { keyedBy: 'name' }, + }, + }, + { + info: { + tags: [{ name: 'Boo', description: 'bar' }, { name: 'Far', description: 'foo' }], + }, + } + ).length + ).toEqual(0); + }); + + test('return results if values are alphabetized', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$.info', + then: { + field: 'tags', + function: RuleFunction.ALPHABETICAL, + }, + }, + { + info: { + tags: ['b', 'a'], + }, + } + ).length + ).toEqual(1); + }); + }); + + describe('objects', () => { + test('returns result if array values are not alphabetized', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$.info', + then: { + function: RuleFunction.ALPHABETICAL, + }, + }, + { + info: { + b: true, + a: true, + }, + } + ).length + ).toEqual(1); + }); + + test('dont return result if object values are alphabetized', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$', + then: { + field: 'info', + function: RuleFunction.ALPHABETICAL, + }, + }, + { + info: { + a: true, + b: true, + }, + } + ).length + ).toEqual(0); + }); + }); + }); + + describe('or', () => { + test('returns result if no properties are present', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$.info', + then: { + function: RuleFunction.OR, + functionOptions: { + properties: ['something-not-present', 'something-else-not-present'], + }, + }, + }, + { + swagger: '2.0', + info: { + version: '1.0.0', + title: 'Swagger Petstore', + }, + } + ).length + ).toEqual(1); + }); + + test('dont returns results if any properties are present', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$.info', + then: { + function: RuleFunction.OR, + functionOptions: { + properties: ['version', 'something-else-not-present'], + }, + }, + }, + { + swagger: '2.0', + info: { + version: '1.0.0', + title: 'Swagger Petstore', + }, + } + ).length + ).toEqual(0); + expect( + applyRuleToObject( + { + summary: '', + given: '$.info', + then: { + function: RuleFunction.OR, + functionOptions: { + properties: ['version', 'title', 'termsOfService'], + }, + }, + }, + { + swagger: '2.0', + info: { + version: '1.0.0', + title: 'Swagger Petstore', + termsOfService: 'http://swagger.io/terms/', + }, + } + ).length + ).toEqual(0); + }); + }); + + describe('xor', () => { + test('returns result if no properties are present', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$.info', + then: { + function: RuleFunction.XOR, + functionOptions: { properties: ['yada-yada', 'whatever'] }, + }, + }, + { + swagger: '2.0', + info: { + version: '1.0.0', + title: 'Swagger Petstore', + termsOfService: 'http://swagger.io/terms/', + }, + } + ).length + ).toEqual(1); + }); + + test('returns result if both properties are present', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$.info', + then: { + function: RuleFunction.XOR, + functionOptions: { properties: ['version', 'title'] }, + }, + }, + { + swagger: '2.0', + info: { + version: '1.0.0', + title: 'Swagger Petstore', + termsOfService: 'http://swagger.io/terms/', + }, + } + ).length + ).toEqual(1); + }); + + test('dont returns results if one of the properties are present', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$.info', + then: { + function: RuleFunction.XOR, + functionOptions: { properties: ['something', 'title'] }, + }, + }, + { + swagger: '2.0', + info: { + version: '1.0.0', + title: 'Swagger Petstore', + termsOfService: 'http://swagger.io/terms/', + }, + } + ).length + ).toEqual(0); + }); + }); + + describe('pattern', () => { + test('returns result if pattern is not matched (on string)', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$.info', + then: { + function: RuleFunction.PATTERN, + functionOptions: { + property: 'termsOfService', + value: '^orange.*$', + }, + }, + }, + { + swagger: '2.0', + info: { + version: '1.0.0', + title: 'Swagger Petstore', + termsOfService: 'http://swagger.io/terms/', + }, + } + ).length + ).toEqual(1); + }); + + test('returns result if pattern is not matched (on object keys)', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$.responses', + then: { + function: RuleFunction.PATTERN, + functionOptions: { property: '*', value: '^[0-9]+$' }, + }, + }, + { + responses: { + '123': { + test: 'something', + }, + '456avbas': { + test: 'something', + }, + '789': { + test: 'something', + }, + }, + } + ).length + ).toEqual(1); + }); + + test('dont return result if pattern is matched (on string)', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$.info', + then: { + function: RuleFunction.PATTERN, + functionOptions: { + property: 'termsOfService', + value: '^http.*$', + }, + }, + }, + { + swagger: '2.0', + info: { + version: '1.0.0', + title: 'Swagger Petstore', + termsOfService: 'http://swagger.io/terms/', + }, + } + ).length + ).toEqual(0); + }); + + test('dont return result if pattern is matched (on object keys)', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$.responses', + then: { + function: RuleFunction.PATTERN, + functionOptions: { property: '*', value: '^[0-9]+$' }, + }, + }, + { + responses: { + '123': { + test: 'something', + }, + '456': { + test: 'something', + }, + '789': { + test: 'something', + }, + }, + } + ).length + ).toEqual(0); + }); + }); + + describe('notContain', () => { + test('returns result if property contains value', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$..*', + then: { + function: RuleFunction.NOT_CONTAIN, + functionOptions: { + properties: ['description'], + value: 'console.log('sup homie');", + }, + } + ).length + ).toEqual(1); + }); + + test('dont return results if property doesnt contain value', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$..*', + then: { + function: RuleFunction.NOT_CONTAIN, + functionOptions: { + properties: ['description'], + value: ' { + test('return result if property ends with value', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$.servers', + then: { + function: RuleFunction.NOT_END_WITH, + functionOptions: { property: 'url', value: '/' }, + }, + }, + { + swagger: '2.0', + servers: [ + { + url: 'http://localhost:5000/', + description: 'Development server', + }, + { + url: 'https://rooms-staging.wework.com', + description: 'Staging server', + }, + ], + } + ).length + ).toEqual(1); + }); + + test('dont return result if property doesnt end with value', () => { + expect( + applyRuleToObject( + { + summary: '', + given: '$.servers', + then: { + function: RuleFunction.NOT_END_WITH, + functionOptions: { property: 'url', value: '/' }, + }, + }, + { + swagger: '2.0', + servers: [ + { + url: 'http://localhost:5000', + description: 'Development server', + }, + { + url: 'https://rooms-staging.wework.com', + description: 'Staging server', + }, + ], + } + ).length + ).toEqual(0); + }); + }); + + describe('maxLength', () => { + test('return result if property is longer than value', () => { + expect( + applyRuleToObject( + { + summary: 'summary should be short (description can be long)', + given: '$..summary', + then: { + function: RuleFunction.MAX_LENGTH, + functionOptions: { value: 20 }, + }, + }, + { + paths: { + '/rooms/{room_id}/reserve/': { + post: { + summary: 'Book Room Really fsdasddssdfgfdhdsafhsad fsad flong fjkdhfsds', + }, + }, + }, + } + ).length + ).toEqual(1); + }); + + test('dont return result if property is shorter than value', () => { + expect( + applyRuleToObject( + { + summary: 'summary should be short (description can be long)', + given: '$..summary', + then: { + function: RuleFunction.MAX_LENGTH, + functionOptions: { value: 20 }, + }, + }, + { + paths: { + '/rooms/{room_id}/reserve/': { + post: { + summary: 'Book', + }, + }, + }, + } + ).length + ).toEqual(0); + }); + }); +}); diff --git a/src/__tests__/linter.ts b/src/__tests__/linter.ts new file mode 100644 index 000000000..7cd319f48 --- /dev/null +++ b/src/__tests__/linter.ts @@ -0,0 +1,244 @@ +import { Spectral } from '../spectral'; + +const fnName = 'fake'; +const fnName2 = 'fake2'; +const spectral = new Spectral(); +const target = { + responses: { + 200: { + description: 'a', + }, + 201: { + description: 'b', + }, + 300: { + description: 'c', + }, + }, +}; +const rules = { + example: { + summary: '', + given: '$.responses', + then: { + function: fnName, + }, + }, +}; + +describe('linter', () => { + describe('functional tests for the when statement', () => { + let fakeLintingFunction: any; + + beforeEach(() => { + fakeLintingFunction = jest.fn(); + spectral.addFunctions({ + [fnName]: fakeLintingFunction, + }); + spectral.addRules(rules); + }); + + describe('given no when', () => { + test('should simply lint anything it matches in the given', () => { + spectral.run(target); + + expect(fakeLintingFunction).toHaveBeenCalledTimes(1); + expect(fakeLintingFunction.mock.calls[0][0]).toEqual(target.responses); + }); + }); + + describe('given when with no pattern and regular field', () => { + test('should call linter if when field exists', () => { + spectral.mergeRules({ + example: { + when: { + field: '200.description', + }, + }, + }); + spectral.run(target); + + expect(fakeLintingFunction).toHaveBeenCalledTimes(1); + expect(fakeLintingFunction.mock.calls[0][0]).toEqual({ + '200': { description: 'a' }, + '201': { description: 'b' }, + '300': { description: 'c' }, + }); + }); + + test('should not call linter if when field not exist', () => { + spectral.mergeRules({ + example: { + when: { + field: '302.description', + }, + }, + }); + spectral.run(target); + + expect(fakeLintingFunction).toHaveBeenCalledTimes(0); + }); + }); + + describe('given when with no pattern and @key field', () => { + test('should call linter if object has ANY keys', () => { + spectral.mergeRules({ + example: { + when: { + field: '@key', + }, + }, + }); + spectral.run(target); + + expect(fakeLintingFunction).toHaveBeenCalledTimes(1); + expect(fakeLintingFunction.mock.calls[0][0]).toEqual({ + '200': { description: 'a' }, + '201': { description: 'b' }, + '300': { description: 'c' }, + }); + }); + + test('should NOT call linter if object has NO keys', () => { + spectral.mergeRules({ + example: { + when: { + field: '@key', + }, + }, + }); + spectral.run({ + responses: {}, + }); + + expect(fakeLintingFunction).toHaveBeenCalledTimes(0); + }); + }); + + describe('given "when" with a pattern and regular field', () => { + test('should NOT lint if pattern does not match', () => { + spectral.mergeRules({ + example: { + when: { + field: '200.description', + pattern: 'X', + }, + }, + }); + spectral.run(target); + + expect(fakeLintingFunction).toHaveBeenCalledTimes(0); + }); + + test('should lint if pattern does match', () => { + spectral.mergeRules({ + example: { + when: { + field: '200.description', + pattern: 'a', + }, + }, + }); + spectral.run(target); + + expect(fakeLintingFunction).toHaveBeenCalledTimes(1); + expect(fakeLintingFunction.mock.calls[0][0]).toEqual({ + '200': { description: 'a' }, + '201': { description: 'b' }, + '300': { description: 'c' }, + }); + }); + }); + + describe('given "when" with a pattern and @key field', () => { + test('should lint ONLY part of object that matches pattern', () => { + spectral.mergeRules({ + example: { + when: { + field: '@key', + pattern: '2..', + }, + }, + }); + spectral.run(target); + + expect(fakeLintingFunction).toHaveBeenCalledTimes(1); + expect(fakeLintingFunction.mock.calls[0][0]).toEqual({ + '200': { description: 'a' }, + '201': { description: 'b' }, + }); + }); + + test('should work with arrays', () => { + spectral.mergeRules({ + example: { + when: { + field: '@key', + pattern: '[02]', + }, + }, + }); + + spectral.run({ + responses: ['a', 'b', 'c', 'd', 'e'], + }); + + expect(fakeLintingFunction).toHaveBeenCalledTimes(1); + expect(fakeLintingFunction.mock.calls[0][0]).toEqual(['a', 'c']); + }); + }); + }); + + describe('functional tests for the then statement', () => { + let fakeLintingFunction: any; + let fakeLintingFunction2: any; + + beforeEach(() => { + fakeLintingFunction = jest.fn(); + fakeLintingFunction2 = jest.fn(); + spectral.addFunctions({ + [fnName]: fakeLintingFunction, + [fnName2]: fakeLintingFunction2, + }); + spectral.addRules({ + example: { + summary: '', + given: '$.responses', + then: [ + { + function: fnName, + functionOptions: { + func1Prop: '1', + }, + }, + { + field: '200', + function: fnName2, + functionOptions: { + func2Prop: '2', + }, + }, + ], + }, + }); + }); + + describe('given list of then objects', () => { + test('should call each one with the appropriate args', () => { + spectral.run(target); + + expect(fakeLintingFunction).toHaveBeenCalledTimes(1); + expect(fakeLintingFunction.mock.calls[0][0]).toEqual(target.responses); + expect(fakeLintingFunction.mock.calls[0][1]).toEqual({ + func1Prop: '1', + }); + + expect(fakeLintingFunction2).toHaveBeenCalledTimes(1); + expect(fakeLintingFunction2.mock.calls[0][0]).toEqual(target.responses['200']); + expect(fakeLintingFunction2.mock.calls[0][1]).toEqual({ + func2Prop: '2', + }); + }); + }); + }); +}); diff --git a/src/__tests__/spectral.test.ts b/src/__tests__/spectral.test.ts index f7be34cdb..6733f80cd 100644 --- a/src/__tests__/spectral.test.ts +++ b/src/__tests__/spectral.test.ts @@ -1,303 +1,72 @@ const merge = require('lodash/merge'); -import { defaultFunctions, defaultRules } from '../rulesets'; import { Spectral } from '../spectral'; -import { RuleFunction, RuleType } from '../types'; - -const todosPartialDeref = require('./fixtures/todos.partial-deref.oas2.json'); - -const aDefaultRuleset = { - functions: defaultFunctions(), - rules: defaultRules(), -}; +import { RuleFunction } from '../types'; describe('spectral', () => { - test('load and run the default rule set', () => { - const s = new Spectral(); - if (aDefaultRuleset.functions) { - s.setFunctions(aDefaultRuleset.functions); - } - s.setRules(aDefaultRuleset.rules); - - const result = s.run(todosPartialDeref, { format: 'oas2' }); - expect(result.results.length).toBeGreaterThan(0); - }); - - // Assures: https://stoplightio.atlassian.net/browse/SL-786 - test('setting/updating rules should not mutate the original ruleset', () => { + test('adding/merging rules should not mutate the passing in rules object', () => { const givenCustomRuleSet = { - rules: { - oas2: { - rule1: { - type: RuleType.STYLE, - then: { - function: RuleFunction.TRUTHY, - functionOptions: { - properties: 'something', - }, - }, - given: '$', - enabled: true, - summary: '', + rule1: { + summary: '', + given: '$', + then: { + function: RuleFunction.TRUTHY, + functionOptions: { + properties: 'something', }, }, }, }; + // deep copy const expectedCustomRuleSet = merge({}, givenCustomRuleSet); const s = new Spectral(); - s.setRules(givenCustomRuleSet.rules); + s.addRules(givenCustomRuleSet); s.mergeRules({ - oas2: { - rule1: false, - }, + rule1: false, }); expect(expectedCustomRuleSet).toEqual(givenCustomRuleSet); }); - // Assures: https://stoplightio.atlassian.net/browse/SL-789 - test('setRules should overwrite the current ruleset', () => { - const ruleset = { - rules: { - format: { - rule1: { - type: RuleType.STYLE, - then: { - function: RuleFunction.TRUTHY, - functionOptions: { - properties: 'something', - }, - }, - given: '$', - enabled: true, - summary: '', - }, - }, - }, - }; - // deep copy + test('mergeRules should update/append on the current rules', () => { const s = new Spectral(); - s.setRules(ruleset.rules); - s.setRules({ - differentFormat: { - rule2: { - type: RuleType.STYLE, - then: { - function: RuleFunction.TRUTHY, - functionOptions: { - properties: 'a different rule', - }, - }, - given: '$', - enabled: true, - summary: '', - }, - }, - }); - expect(s.getRules()).toHaveLength(1); - expect(s.getRules()).toMatchInlineSnapshot(` -Array [ - Object { - "apply": [Function], - "format": "differentFormat", - "name": "rule2", - "rule": Object { - "enabled": true, - "given": "$", - "summary": "", - "then": Object { - "function": "truthy", - "functionOptions": Object { - "properties": "a different rule", - }, - }, - "type": "style", - }, - }, -] -`); - }); - - // Assures: https://stoplightio.atlassian.net/browse/SL-789 - test('updateRules should update/append the current ruleset', () => { - const ruleset = { - rules: { - format: { - rule1: { - type: RuleType.STYLE, - then: { - function: RuleFunction.TRUTHY, - functionOptions: { - properties: 'something', - }, - }, - given: '$', - enabled: true, - summary: '', + s.addRules({ + rule1: { + summary: '', + given: '$', + then: { + function: RuleFunction.TRUTHY, + functionOptions: { + properties: 'something', }, }, }, - }; - // deep copy - const s = new Spectral(); - s.setRules(ruleset.rules); + }); s.mergeRules({ - differentFormat: { - rule2: { - type: RuleType.STYLE, - then: { - function: RuleFunction.TRUTHY, - functionOptions: { - properties: 'a different rule', - }, + rule2: { + summary: '', + given: '$', + then: { + function: RuleFunction.TRUTHY, + functionOptions: { + properties: 'a different rule', }, - given: '$', - enabled: true, - summary: '', }, }, }); - expect(s.getRules()).toHaveLength(2); + expect(Object.keys(s.rules)).toEqual(['rule1', 'rule2']); s.mergeRules({ - format: { - rule1: false, - }, + rule1: false, }); - expect(s.getRules()).toHaveLength(2); - }); - - // Assures: https://stoplightio.atlassian.net/browse/SL-787 - test('given a ruleset with two identical rules under two distinct formats should not collide', () => { - const rulesets = [ - { - rules: { - oas2: { - ruleName1: { - type: RuleType.STYLE, - then: { - function: RuleFunction.TRUTHY, - functionOptions: { - properties: 'something-different', - }, - }, - given: '$', - enabled: true, - summary: '', - }, - }, - oas3: { - ruleName1: { - type: RuleType.STYLE, - then: { - function: RuleFunction.NOT_CONTAIN, - functionOptions: { - properties: ['url'], - value: 'gruntjs', - }, - }, - given: '$.license', - enabled: false, - summary: '', - }, - }, - }, - }, - ]; - - const s = new Spectral(); - s.setRules(rulesets[0].rules); - - expect(s.getRules('oas2')).toMatchInlineSnapshot(` -Array [ - Object { - "apply": [Function], - "format": "oas2", - "name": "ruleName1", - "rule": Object { - "enabled": true, - "given": "$", - "summary": "", - "then": Object { - "function": "truthy", - "functionOptions": Object { - "properties": "something-different", - }, - }, - "type": "style", - }, - }, -] -`); - expect(s.getRules('oas3')).toMatchInlineSnapshot(` -Array [ - Object { - "apply": [Function], - "format": "oas3", - "name": "ruleName1", - "rule": Object { - "enabled": false, - "given": "$.license", - "summary": "", - "then": Object { - "function": "notContain", - "functionOptions": Object { - "properties": Array [ - "url", - ], - "value": "gruntjs", - }, - }, - "type": "style", - }, - }, -] -`); - }); - - test('getRules returns a flattened list of rules filtered by format', () => { - const rules = { - oas2: { - rule1: { - type: RuleType.STYLE, - then: { - function: RuleFunction.TRUTHY, - functionOptions: { - properties: 'something-not-present', - }, - }, - given: '$', - enabled: false, - summary: '', - }, - }, - oas3: { - rule3: { - type: RuleType.STYLE, - then: { - function: RuleFunction.TRUTHY, - functionOptions: { - properties: 'something-not-present', - }, - }, - given: '$', - enabled: false, - summary: '', - }, - }, - }; - - const s = new Spectral(); - s.setRules(rules); - const results = s.getRules('oas2'); - - expect(results.length).toBe(1); + expect(Object.keys(s.rules)).toEqual(['rule1', 'rule2']); + expect(s.rules.rule1.enabled).toBe(false); }); }); diff --git a/src/__tests__/style.test.ts b/src/__tests__/style.test.ts deleted file mode 100644 index 7ed95f1d7..000000000 --- a/src/__tests__/style.test.ts +++ /dev/null @@ -1,553 +0,0 @@ -import { Spectral } from '../spectral'; -import { IRuleResult, Rule, RuleFunction, RuleType } from '../types'; - -const applyRuleToObject = (r: Rule, o: object): IRuleResult[] => { - const s = new Spectral(); - s.setRules({ - testing: { - 'test:rule': r, - }, - }); - return s.run(o, { format: 'testing' }).results; -}; - -describe('lint', () => { - describe('rules', () => { - describe('truthy', () => { - test('returns result if value is not present', () => { - expect( - applyRuleToObject( - { - type: RuleType.STYLE, - then: { - function: RuleFunction.TRUTHY, - functionOptions: { properties: 'something-not-present' }, - }, - given: '$.info', - enabled: true, - summary: '', - }, - { - swagger: '2.0', - info: { - version: '1.0.0', - title: 'Swagger Petstore', - termsOfService: 'http://swagger.io/terms/', - }, - } - ).length - ).toEqual(1); - }); - - test('doesnt return result if value is present', () => { - expect( - applyRuleToObject( - { - type: RuleType.STYLE, - then: { - function: RuleFunction.TRUTHY, - functionOptions: { properties: 'version' }, - }, - given: '$.info', - enabled: true, - summary: '', - }, - { - swagger: '2.0', - info: { - version: '1.0.0', - title: 'Swagger Petstore', - termsOfService: 'http://swagger.io/terms/', - }, - } - ).length - ).toEqual(0); - }); - }); - - describe('alphabetical', () => { - test('returns result if values are not alphabetized', () => { - expect( - applyRuleToObject( - { - type: RuleType.STYLE, - then: { - function: RuleFunction.ALPHABETICAL, - functionOptions: { properties: 'tags', keyedBy: 'name' }, - }, - given: '$.info', - enabled: true, - summary: '', - }, - { - swagger: '2.0', - info: { - version: '1.0.0', - title: 'Swagger Petstore', - tags: [{ name: 'Far', description: 'bar' }, { name: 'Boo', description: 'foo' }], - }, - } - ).length - ).toEqual(1); - }); - - test('dont return result if values are alphabetized', () => { - expect( - applyRuleToObject( - { - type: RuleType.STYLE, - then: { - function: RuleFunction.ALPHABETICAL, - functionOptions: { properties: 'tags', keyedBy: 'name' }, - }, - given: '$.info', - enabled: true, - summary: '', - }, - { - swagger: '2.0', - info: { - version: '1.0.0', - title: 'Swagger Petstore', - tags: [{ name: 'Boo', description: 'bar' }, { name: 'Far', description: 'foo' }], - }, - } - ).length - ).toEqual(0); - }); - }); - - describe('or', () => { - test('returns result if no properties are present', () => { - expect( - applyRuleToObject( - { - type: RuleType.STYLE, - then: { - function: RuleFunction.OR, - functionOptions: { properties: ['something-not-present', 'something-else-not-present'] }, - }, - given: '$.info', - enabled: true, - summary: '', - }, - { - swagger: '2.0', - info: { - version: '1.0.0', - title: 'Swagger Petstore', - }, - } - ).length - ).toEqual(1); - }); - - test('dont returns results if any properties are present', () => { - expect( - applyRuleToObject( - { - type: RuleType.STYLE, - then: { - function: RuleFunction.OR, - functionOptions: { properties: ['version', 'something-else-not-present'] }, - }, - given: '$.info', - enabled: true, - summary: '', - }, - { - swagger: '2.0', - info: { - version: '1.0.0', - title: 'Swagger Petstore', - }, - } - ).length - ).toEqual(0); - expect( - applyRuleToObject( - { - type: RuleType.STYLE, - then: { - function: RuleFunction.OR, - functionOptions: { properties: ['version', 'title', 'termsOfService'] }, - }, - given: '$.info', - enabled: true, - summary: '', - }, - { - swagger: '2.0', - info: { - version: '1.0.0', - title: 'Swagger Petstore', - termsOfService: 'http://swagger.io/terms/', - }, - } - ).length - ).toEqual(0); - }); - }); - - describe('xor', () => { - test('returns result if no properties are present', () => { - expect( - applyRuleToObject( - { - type: RuleType.STYLE, - then: { - function: RuleFunction.XOR, - functionOptions: { properties: ['yada-yada', 'whatever'] }, - }, - given: '$.info', - enabled: true, - summary: '', - }, - { - swagger: '2.0', - info: { - version: '1.0.0', - title: 'Swagger Petstore', - termsOfService: 'http://swagger.io/terms/', - }, - } - ).length - ).toEqual(1); - }); - - test('returns result if both properties are present', () => { - expect( - applyRuleToObject( - { - type: RuleType.STYLE, - then: { - function: RuleFunction.XOR, - functionOptions: { properties: ['version', 'title'] }, - }, - given: '$.info', - enabled: true, - summary: '', - }, - { - swagger: '2.0', - info: { - version: '1.0.0', - title: 'Swagger Petstore', - termsOfService: 'http://swagger.io/terms/', - }, - } - ).length - ).toEqual(1); - }); - - test('dont returns results if one of the properties are present', () => { - expect( - applyRuleToObject( - { - type: RuleType.STYLE, - then: { - function: RuleFunction.XOR, - functionOptions: { properties: ['something', 'title'] }, - }, - given: '$.info', - enabled: true, - summary: '', - }, - { - swagger: '2.0', - info: { - version: '1.0.0', - title: 'Swagger Petstore', - termsOfService: 'http://swagger.io/terms/', - }, - } - ).length - ).toEqual(0); - }); - }); - - describe('pattern', () => { - test('returns result if pattern is not matched (on string)', () => { - expect( - applyRuleToObject( - { - type: RuleType.STYLE, - then: { - function: RuleFunction.PATTERN, - functionOptions: { property: 'termsOfService', value: '^orange.*$' }, - }, - given: '$.info', - enabled: true, - summary: '', - }, - { - swagger: '2.0', - info: { - version: '1.0.0', - title: 'Swagger Petstore', - termsOfService: 'http://swagger.io/terms/', - }, - } - ).length - ).toEqual(1); - }); - - test('returns result if pattern is not matched (on object keys)', () => { - expect( - applyRuleToObject( - { - type: RuleType.STYLE, - then: { - function: RuleFunction.PATTERN, - functionOptions: { property: '*', value: '^[0-9]+$' }, - }, - given: '$.responses', - enabled: true, - summary: '', - }, - { - responses: { - '123': { - test: 'something', - }, - '456avbas': { - test: 'something', - }, - '789': { - test: 'something', - }, - }, - } - ).length - ).toEqual(1); - }); - - test('dont return result if pattern is matched (on string)', () => { - expect( - applyRuleToObject( - { - type: RuleType.STYLE, - then: { - function: RuleFunction.PATTERN, - functionOptions: { property: 'termsOfService', value: '^http.*$' }, - }, - given: '$.info', - enabled: true, - summary: '', - }, - { - swagger: '2.0', - info: { - version: '1.0.0', - title: 'Swagger Petstore', - termsOfService: 'http://swagger.io/terms/', - }, - } - ).length - ).toEqual(0); - }); - - test('dont return result if pattern is matched (on object keys)', () => { - expect( - applyRuleToObject( - { - type: RuleType.STYLE, - then: { - function: RuleFunction.PATTERN, - functionOptions: { property: '*', value: '^[0-9]+$' }, - }, - given: '$.responses', - enabled: true, - summary: '', - }, - { - responses: { - '123': { - test: 'something', - }, - '456': { - test: 'something', - }, - '789': { - test: 'something', - }, - }, - } - ).length - ).toEqual(0); - }); - }); - - describe('notContain', () => { - test('returns result if property contains value', () => { - expect( - applyRuleToObject( - { - type: RuleType.STYLE, - then: { - function: RuleFunction.NOT_CONTAIN, - functionOptions: { properties: ['description'], value: 'console.log('sup homie');", - }, - } - ).length - ).toEqual(1); - }); - - test('dont return results if property doesnt contain value', () => { - expect( - applyRuleToObject( - { - type: RuleType.STYLE, - then: { - function: RuleFunction.NOT_CONTAIN, - functionOptions: { properties: ['description'], value: ' { - test('return result if property ends with value', () => { - expect( - applyRuleToObject( - { - type: RuleType.STYLE, - then: { - function: RuleFunction.NOT_END_WITH, - functionOptions: { property: 'url', value: '/' }, - }, - given: '$.servers', - enabled: true, - summary: '', - }, - { - swagger: '2.0', - servers: [ - { - url: 'http://localhost:5000/', - description: 'Development server', - }, - { - url: 'https://rooms-staging.wework.com', - description: 'Staging server', - }, - ], - } - ).length - ).toEqual(1); - }); - - test('dont return result if property doesnt end with value', () => { - expect( - applyRuleToObject( - { - type: RuleType.STYLE, - then: { - function: RuleFunction.NOT_END_WITH, - functionOptions: { property: 'url', value: '/' }, - }, - given: '$.servers', - enabled: true, - summary: '', - }, - { - swagger: '2.0', - servers: [ - { - url: 'http://localhost:5000', - description: 'Development server', - }, - { - url: 'https://rooms-staging.wework.com', - description: 'Staging server', - }, - ], - } - ).length - ).toEqual(0); - }); - }); - - describe('maxLength', () => { - test('return result if property is longer than value', () => { - expect( - applyRuleToObject( - { - type: RuleType.STYLE, - then: { - function: RuleFunction.MAX_LENGTH, - functionOptions: { value: 20 }, - }, - given: '$..summary', - enabled: true, - description: 'summary should be short (description can be long)', - summary: '', - }, - { - paths: { - '/rooms/{room_id}/reserve/': { - post: { - summary: 'Book Room Really fsdasddssdfgfdhdsafhsad fsad flong fjkdhfsds', - }, - }, - }, - } - ).length - ).toEqual(1); - }); - - test('dont return result if property is shorter than value', () => { - expect( - applyRuleToObject( - { - type: RuleType.STYLE, - then: { - function: RuleFunction.MAX_LENGTH, - functionOptions: { value: 20 }, - }, - given: '$..summary', - enabled: true, - description: 'summary should be short (description can be long)', - summary: '', - }, - { - paths: { - '/rooms/{room_id}/reserve/': { - post: { - summary: 'Book', - }, - }, - }, - } - ).length - ).toEqual(0); - }); - }); - }); -}); diff --git a/src/__tests__/when.test.ts b/src/__tests__/when.test.ts deleted file mode 100644 index 62c3e0b17..000000000 --- a/src/__tests__/when.test.ts +++ /dev/null @@ -1,264 +0,0 @@ -import { merge } from 'lodash'; -import { Spectral } from '../spectral'; -import { RuleType } from '../types'; - -const format = 'oas'; -const fnName = 'fake'; -const spectral = new Spectral(); -const target = { - responses: { - 200: { - description: 'a', - }, - 201: { - description: 'b', - }, - 300: { - description: 'c', - }, - }, -}; -const rules = { - [format]: { - example: { - description: '', - enabled: true, - given: '$.responses', - summary: '', - type: RuleType.VALIDATION, - then: { - function: fnName, - }, - }, - }, -}; - -describe('functional tests for the when statement', () => { - let fakeLintingFunction: any; - - beforeEach(() => { - fakeLintingFunction = jest.fn(); - spectral.setFunctions({ - [fnName]: fakeLintingFunction, - }); - }); - - describe('given no when', () => { - test('should simply lint anything it matches in the given', () => { - spectral.setRules(rules); - - spectral.run(target, { - format, - }); - - expect(fakeLintingFunction).toHaveBeenCalledTimes(1); - expect(fakeLintingFunction.mock.calls[0][0].object).toEqual({ - '200': { description: 'a' }, - '201': { description: 'b' }, - '300': { description: 'c' }, - }); - }); - }); - - describe('given when with no pattern and regular field', () => { - test('should call linter if when field exists', () => { - spectral.setRules( - merge({}, rules, { - [format]: { - example: { - when: { - field: '200.description', - }, - }, - }, - }) - ); - - spectral.run(target, { - format, - }); - - expect(fakeLintingFunction).toHaveBeenCalledTimes(1); - expect(fakeLintingFunction.mock.calls[0][0].object).toEqual({ - '200': { description: 'a' }, - '201': { description: 'b' }, - '300': { description: 'c' }, - }); - }); - - test('should not call linter if when field not exist', () => { - spectral.setRules( - merge({}, rules, { - [format]: { - example: { - when: { - field: '302.description', - }, - }, - }, - }) - ); - - spectral.run(target, { - format, - }); - - expect(fakeLintingFunction).toHaveBeenCalledTimes(0); - }); - }); - - describe('given when with no pattern and @key field', () => { - test('should call linter if object has ANY keys', () => { - spectral.setRules( - merge({}, rules, { - [format]: { - example: { - when: { - field: '@key', - }, - }, - }, - }) - ); - - spectral.run(target, { - format, - }); - - expect(fakeLintingFunction).toHaveBeenCalledTimes(1); - expect(fakeLintingFunction.mock.calls[0][0].object).toEqual({ - '200': { description: 'a' }, - '201': { description: 'b' }, - '300': { description: 'c' }, - }); - }); - - test('should NOT call linter if object has NO keys', () => { - spectral.setRules( - merge({}, rules, { - [format]: { - example: { - when: { - field: '@key', - }, - }, - }, - }) - ); - - spectral.run( - { - responses: {}, - }, - { - format, - } - ); - - expect(fakeLintingFunction).toHaveBeenCalledTimes(0); - }); - }); - - describe('given "when" with a pattern and regular field', () => { - test('should NOT lint if pattern does not match', () => { - spectral.setRules( - merge({}, rules, { - [format]: { - example: { - when: { - field: '200.description', - pattern: 'X', - }, - }, - }, - }) - ); - - spectral.run(target, { - format, - }); - - expect(fakeLintingFunction).toHaveBeenCalledTimes(0); - }); - - test('should lint if pattern does match', () => { - spectral.setRules( - merge({}, rules, { - [format]: { - example: { - when: { - field: '200.description', - pattern: 'a', - }, - }, - }, - }) - ); - - spectral.run(target, { - format, - }); - - expect(fakeLintingFunction).toHaveBeenCalledTimes(1); - expect(fakeLintingFunction.mock.calls[0][0].object).toEqual({ - '200': { description: 'a' }, - '201': { description: 'b' }, - '300': { description: 'c' }, - }); - }); - }); - - describe('given "when" with a pattern and @key field', () => { - test('should lint ONLY part of object that matches pattern', () => { - spectral.setRules( - merge({}, rules, { - [format]: { - example: { - when: { - field: '@key', - pattern: '2..', - }, - }, - }, - }) - ); - - spectral.run(target, { - format, - }); - - expect(fakeLintingFunction).toHaveBeenCalledTimes(1); - expect(fakeLintingFunction.mock.calls[0][0].object).toEqual({ - '200': { description: 'a' }, - '201': { description: 'b' }, - }); - }); - - test('should work with arrays', () => { - spectral.setRules( - merge({}, rules, { - [format]: { - example: { - when: { - field: '@key', - pattern: '[02]', - }, - }, - }, - }) - ); - - spectral.run( - { - responses: ['a', 'b', 'c', 'd', 'e'], - }, - { - format, - } - ); - - expect(fakeLintingFunction).toHaveBeenCalledTimes(1); - expect(fakeLintingFunction.mock.calls[0][0].object).toEqual(['a', 'c']); - }); - }); -}); diff --git a/src/functions/alphabetical.ts b/src/functions/alphabetical.ts index 12054e63e..b638a0ff2 100644 --- a/src/functions/alphabetical.ts +++ b/src/functions/alphabetical.ts @@ -1,49 +1,52 @@ -import { IAlphaRule, IRuleFunction, IRuleOpts, IRuleResult } from '../types'; -import { IFunctionPaths } from '../types/spectral'; -import { ensureRule } from './utils/ensureRule'; +import isEqual = require('lodash/isEqual'); -export const alphabetical: IRuleFunction = (opts: IRuleOpts, paths: IFunctionPaths) => { - const results: IRuleResult[] = []; +import { IAlphaRuleOptions, IFunction, IFunctionResult } from '../types'; - const { object, rule } = opts; - const { keyedBy, properties: inputProperties } = rule.then.functionOptions; +export const alphabetical: IFunction = (targetVal, opts) => { + const results: IFunctionResult[] = []; - let properties = inputProperties; - if (properties && !Array.isArray(properties)) { - properties = [properties]; + if (!targetVal) { + return results; } - for (const property of properties) { - if (!object[property] || object[property].length < 2) { - continue; - } + let targetArray: any[] = targetVal; + if (!Array.isArray(targetVal)) { + targetArray = Object.keys(targetVal); + } + + // don't mutate original array + const copiedArray = targetArray.slice(); - const arrayCopy: object[] = object[property].slice(0); + if (copiedArray.length < 2) { + return results; + } - // If we aren't expecting an object keyed by a specific property, then treat the - // object as a simple array. - if (keyedBy) { - arrayCopy.sort((a, b) => { - if (a[keyedBy] < b[keyedBy]) { - return -1; - } else if (a[keyedBy] > b[keyedBy]) { - return 1; - } + const { keyedBy } = opts; + // If we aren't expecting an object keyed by a specific property, then treat the + // object as a simple array. + if (keyedBy) { + copiedArray.sort((a, b) => { + if (typeof a !== 'object') { return 0; - }); - } else { - arrayCopy.sort(); - } - - const res = ensureRule(() => { - object.should.have.property(property); - object[property].should.be.deepEqual(arrayCopy); - }, paths.given); - - if (res) { - results.push(res); - } + } + + if (a[keyedBy] < b[keyedBy]) { + return -1; + } else if (a[keyedBy] > b[keyedBy]) { + return 1; + } + + return 0; + }); + } else { + copiedArray.sort(); + } + + if (!isEqual(targetArray, copiedArray)) { + results.push({ + message: 'properties are not in alphabetical order', + }); } return results; diff --git a/src/functions/truthy.ts b/src/functions/truthy.ts index 2cd4f7af3..77cc51696 100644 --- a/src/functions/truthy.ts +++ b/src/functions/truthy.ts @@ -1,39 +1,16 @@ -import { IRuleFunction, IRuleOpts, IRuleResult, ITruthyRule } from '../types'; -import { ensureRule } from './utils/ensureRule'; +import { IFunction, IFunctionResult, ITruthRuleOptions } from '../types'; -// @ts-ignore -import * as should from 'should/as-function'; -import { IFunctionPaths } from '../types/spectral'; +export const truthy: IFunction = (targetVal, opts) => { + const results: IFunctionResult[] = []; -export const truthy: IRuleFunction = (opts: IRuleOpts, paths: IFunctionPaths) => { - const results: IRuleResult[] = []; - - const { object, rule } = opts; - const { properties: inputProperties, max } = rule.then.functionOptions; - - let properties = inputProperties; + let properties = opts.properties; if (!Array.isArray(properties)) properties = [properties]; for (const property of properties) { - const res = ensureRule(() => { - object.should.have.property(property); - object[property].should.not.be.empty(); - }, paths.given); - - if (res) { - results.push(res); - } - } - - if (max) { - const res = ensureRule(() => { - // Ignore vendor extensions, for reasons like our the resolver adding x-miro - const keys = Object.keys(object).filter(key => !key.startsWith('x-')); - should(keys.length).be.exactly(max); - }, paths.given); - - if (res) { - results.push(res); + if (!targetVal[property]) { + results.push({ + message: `${property} is not truthy`, + }); } } diff --git a/src/linter/index.ts b/src/linter.ts similarity index 58% rename from src/linter/index.ts rename to src/linter.ts index 59f79f441..8ef889b0d 100644 --- a/src/linter/index.ts +++ b/src/linter.ts @@ -1,42 +1,70 @@ import { PathComponent } from 'jsonpath'; -import { filter, omitBy } from 'lodash'; -import { IRule, IRuleOpts, IRuleResult } from '../types'; -import { IRuleEntry, IRunOpts } from '../types/spectral'; const get = require('lodash/get'); const has = require('lodash/has'); +const filter = require('lodash/filter'); +const omitBy = require('lodash/omitBy'); + +import { IFunction, IRuleResult, IRunOpts, IRunRule, IThen } from './types'; // TODO(SO-23): unit test but mock whatShouldBeLinted -export function lintNode( - ruleEntry: IRuleEntry, - opts: IRunOpts, - node: { path: PathComponent[]; value: any } -): IRuleResult[] { - const conditioning = whatShouldBeLinted(node.value, ruleEntry.rule); +export const lintNode = ( + node: { path: PathComponent[]; value: any }, + rule: IRunRule, + then: IThen, + apply: IFunction, + opts: IRunOpts +): IRuleResult[] => { + const conditioning = whatShouldBeLinted(node.value, rule); + // If the 'when' condition is not satisfied, simply don't run the linter if (!conditioning.lint) { return []; } - const opt: IRuleOpts = { - object: conditioning.value, - rule: ruleEntry.rule, - }; + let targetValue = conditioning.value; - if (ruleEntry.rule.given === '$') { - // allow resolved and stringified targets to be passed to rules when operating on - // the root path - if (opts.resolvedTarget) { - opt.resObj = opts.resolvedTarget; - } + // const opt: IRuleOpts = { + // object: conditioning.value, + // rule, + // }; + + // if (rule.given === '$') { + // // allow resolved and stringified targets to be passed to rules when operating on + // // the root path + // if (opts.resolvedTarget) { + // opt.resObj = opts.resolvedTarget; + // } + // } + + if (rule.then && then.field) { + targetValue = get(targetValue, then.field); } - return ruleEntry.apply(opt, { - given: node.path, + const results = + apply( + targetValue, + then.functionOptions || {}, + { + given: node.path, + target: node.path, // todo, node.path + rule.then.field + }, + { + original: {}, + given: node.value, + resolved: opts.resolvedTarget, + } + ) || []; + + return results.map(result => { + return { + path: result.path || node.path, + message: result.message, + }; }); -} +}; // TODO(SO-23): unit test idividually -export function whatShouldBeLinted(originalValue: any, rule: IRule): { lint: boolean; value: any } { +export const whatShouldBeLinted = (originalValue: any, rule: IRunRule): { lint: boolean; value: any } => { const when = rule.when; if (!when) { return { @@ -47,7 +75,8 @@ export function whatShouldBeLinted(originalValue: any, rule: IRule): { lint: boo const pattern = when.pattern; const field = when.field; - // what if someone's field is called '@key'? + + // TODO: what if someone's field is called '@key'? should we use @@key? const isKey = field === '@key'; if (!pattern) { @@ -57,6 +86,7 @@ export function whatShouldBeLinted(originalValue: any, rule: IRule): { lint: boo if (isKey) { return keyAndOptionalPattern(originalValue); } + return { lint: has(originalValue, field), value: originalValue, @@ -73,7 +103,7 @@ export function whatShouldBeLinted(originalValue: any, rule: IRule): { lint: boo lint: fieldValue.match(pattern) !== null, value: originalValue, }; -} +}; function keyAndOptionalPattern(originalValue: any, pattern?: string) { const type = typeof originalValue; @@ -96,7 +126,7 @@ function keyAndOptionalPattern(originalValue: any, pattern?: string) { }; } else if (Array.isArray(originalValue)) { const leanValue = pattern - ? filter(originalValue, (v, index) => { + ? filter(originalValue, (_v: any, index: any) => { return String(index).match(pattern) !== null; }) : originalValue; @@ -106,7 +136,7 @@ function keyAndOptionalPattern(originalValue: any, pattern?: string) { }; } else { const leanValue = pattern - ? omitBy(originalValue, (v, key) => { + ? omitBy(originalValue, (_v: any, key: any) => { return key.match(pattern) === null; }) : originalValue; diff --git a/src/runner.ts b/src/runner.ts new file mode 100644 index 000000000..f9356178b --- /dev/null +++ b/src/runner.ts @@ -0,0 +1,55 @@ +import * as jp from 'jsonpath'; + +import { lintNode } from './linter'; +import { FunctionCollection, IRuleResult, IRunOpts, IRunResult, IRunRule, RunRuleCollection } from './types'; + +export const runRules = ( + target: object, + rules: RunRuleCollection, + functions: FunctionCollection, + opts: IRunOpts +): IRunResult => { + let results: IRuleResult[] = []; + + for (const name in rules) { + const rule = rules[name]; + if (!rule) continue; + + if (rule.hasOwnProperty('enabled') && !rule.enabled) { + continue; + } + + try { + results = results.concat(runRule(target, rule, functions, opts)); + } catch (e) { + console.error(`Unable to run rule '${name}':\n${e}`); + } + } + + return { results }; +}; + +const runRule = (target: object, rule: IRunRule, functions: FunctionCollection, opts: IRunOpts): IRuleResult[] => { + let results: IRuleResult[] = []; + + const nodes = jp.nodes(target, rule.given); + + for (const node of nodes) { + try { + const thens = Array.isArray(rule.then) ? rule.then : [rule.then]; + for (const then of thens) { + const func = functions[then.function]; + if (!func) { + console.warn(`Function ${then.function} not found. Called by rule ${rule.name}.`); + continue; + } + + results = results.concat(lintNode(node, rule, then, func, opts)); + } + } catch (e) { + console.warn(`Encountered error when running rule '${rule.name}' on node at path '${node.path}':\n${e}`); + } + } + + return results; +}; diff --git a/src/spectral.ts b/src/spectral.ts index d45949a0a..1deb13e5c 100644 --- a/src/spectral.ts +++ b/src/spectral.ts @@ -1,147 +1,65 @@ const merge = require('lodash/merge'); -const values = require('lodash/values'); -const compact = require('lodash/compact'); -const flatten = require('lodash/flatten'); -import * as jp from 'jsonpath'; import { functions as defaultFunctions } from './functions'; -import { lintNode } from './linter'; -import * as types from './types'; -import { IFunctionCollection, IRuleCollection, IRuleEntry, IRunOpts, IRunResult } from './types/spectral'; +import { runRules } from './runner'; +import { FunctionCollection, IRunOpts, IRunResult, RuleCollection, RuleDeclaration, RunRuleCollection } from './types'; export class Spectral { - // normalized object for holding rule definitions indexed by ${format}-${name} - private _rulesByIndex: IRuleCollection = {}; - private _functionCollection: IFunctionCollection = defaultFunctions; + private _rules: RuleCollection = {}; + private _functions: FunctionCollection = defaultFunctions; - // TODO needs better pattern matching - public getRules(dataFormat?: string): IRuleEntry[] { - const rules = []; - - for (const name in this._rulesByIndex) { - if (!this._rulesByIndex.hasOwnProperty(name)) continue; - const { name: rName, rule, format, apply } = this._rulesByIndex[name]; - - if (!dataFormat || format === dataFormat) { - rules.push({ name: rName, format, rule, apply }); - } - } - - return rules; + public run(target: object, opts: IRunOpts = {}): IRunResult { + return runRules(target, this.rules, this.functions, opts); } - public setFunctions(functionCollection: IFunctionCollection) { - this._functionCollection = merge({}, defaultFunctions, functionCollection); - } - - public mergeFunctions(functionCollection: IFunctionCollection) { - this._functionCollection = merge({}, this._functionCollection, functionCollection); - } + /** + * Functions + */ - public setRules(ruleStore: types.IRuleStore) { - this._rulesByIndex = this.toRuleCollection(ruleStore, {}); + public get functions(): FunctionCollection { + return this._functions; } - public mergeRules(ruleStore: types.IRuleStore) { - this._rulesByIndex = this.toRuleCollection(ruleStore, merge({}, this._rulesByIndex)); + public addFunctions(functions: FunctionCollection) { + Object.assign(this._functions, merge({}, functions)); } - private toRuleCollection(ruleStore: types.IRuleStore, internalRuleStore: IRuleCollection): IRuleCollection { - for (const format of Object.keys(ruleStore)) { - for (const ruleName of Object.keys(ruleStore[format])) { - const rule = ruleStore[format][ruleName]; - const ruleIndex = this.toRuleIndex(ruleName, format); - if (typeof rule === 'boolean') { - // enabling/disabling rule - if (!internalRuleStore[ruleIndex]) { - console.warn( - `Unable to find rule matching name '${ruleName}' under format ${format} - this entry has no effect` - ); - continue; - } - - internalRuleStore[ruleIndex].rule.enabled = rule; - } else if (typeof rule === 'object' && !Array.isArray(rule)) { - // rule definition - internalRuleStore[ruleIndex] = this.parseRuleDefinition(ruleName, format, rule); - } else { - throw new Error(`Unknown rule definition format: ${rule}`); - } - } - } + /** + * Rules + */ - return internalRuleStore; - } + public get rules(): RunRuleCollection { + const rules: RunRuleCollection = {}; - public run(target: object, opts: IRunOpts): IRunResult { - return { - results: this.runAllLinters(target, this._rulesByIndex, opts), - }; - } + for (const name in this._rules) { + const rule = this._rules[name]; - private runAllLinters(target: object, ruleStore: IRuleCollection, opts: IRunOpts): types.IRuleResult[] { - return flatten( - compact( - values(ruleStore).map((ruleEntry: IRuleEntry) => { - if ( - !ruleEntry.rule.enabled || - (opts.type && ruleEntry.rule.type !== opts.type) || - ruleEntry.format !== opts.format - ) { - return null; - } + rules[name] = { + name, + ...rule, + }; + } - try { - return this.lintNodes(target, ruleEntry, opts); - } catch (e) { - console.error(`Unable to run rule '${ruleEntry.name}':\n${e}`); - return null; - } - }) - ) - ); + return rules; } - private lintNodes(target: object, ruleEntry: IRuleEntry, opts: IRunOpts): types.IRuleResult[] { - const nodes = jp.nodes(target, ruleEntry.rule.given); - return flatten( - compact( - nodes.map(node => { - const { path: nPath } = node; - try { - return lintNode(ruleEntry, opts, node); - } catch (e) { - console.warn(`Encountered error when running rule '${ruleEntry.name}' on node at path '${nPath}':\n${e}`); - return null; - } - }) - ) - ); + public addRules(rules: RuleCollection) { + Object.assign(this._rules, merge({}, rules)); } - private parseRuleDefinition(name: string, format: string, rule: types.Rule): IRuleEntry { - const ruleIndex = this.toRuleIndex(name, format); - try { - jp.parse(rule.given); - } catch (e) { - throw new SyntaxError(`Invalid JSON path for rule '${ruleIndex}': ${rule.given}\n\n${e}`); - } - - const ruleFunc = this._functionCollection[rule.then.function]; + public mergeRules(rules: RuleDeclaration) { + for (const ruleName in merge({}, rules)) { + const rule = rules[ruleName]; + if (typeof rule === 'boolean') { + if (!this._rules[ruleName]) { + console.warn(`Unable to find rule matching name '${ruleName}' - this merge entry has no effect`); + continue; + } - if (!ruleFunc) { - throw new SyntaxError(`Function does not exist for rule '${ruleIndex}': ${rule.then.function}`); + this._rules[ruleName].enabled = rule; + } else { + this._rules[ruleName] = merge(this._rules[ruleName], rule); + } } - - return { - name, - format, - rule: rule as types.Rule, - apply: ruleFunc, - }; - } - - private toRuleIndex(ruleName: string, ruleFormat: string) { - return `${ruleFormat}-${ruleName}`; } } diff --git a/src/types/function.ts b/src/types/function.ts new file mode 100644 index 000000000..a3a6b1421 --- /dev/null +++ b/src/types/function.ts @@ -0,0 +1,24 @@ +import { ObjPath } from '@stoplight/types'; + +export type IFunction = ( + targetValue: any, + options: O, + paths: IFunctionPaths, + otherValues: IFunctionValues +) => void | IFunctionResult[]; + +export interface IFunctionPaths { + given: ObjPath; + target?: ObjPath; +} + +export interface IFunctionValues { + original: any; + given: any; + resolved?: any; +} + +export interface IFunctionResult { + message: string; + path?: ObjPath; +} diff --git a/src/types/index.ts b/src/types/index.ts index 34aacb6ea..53c47a3f4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,46 +1,4 @@ -import { ErrorObject } from 'ajv'; -import { AssertionError } from 'assert'; - -// import { ValidationSeverity, RuleType } from './enums'; -import { ObjPath } from '@stoplight/types/parsers'; -import { Rule } from './rule'; -import { IFunctionPaths } from './spectral'; - -export type TargetSpec = 'oas2' | 'oas3' | string; -export type RawResult = ErrorObject | AssertionError; -export type Path = Array; - -export interface IRuleOpts { - object: any; - strObj?: string; - resObj?: any; - rule: I; -} - -export type IRuleFunction = (opts: IRuleOpts, paths: IFunctionPaths) => IRuleResult[]; - -// export interface IRuleResult extends IValidationResult { -export interface IRuleResult { - path: ObjPath; - message: string; - // type: RuleType; -} - -export interface IRuleStore { - /** - * index is a simplified regex of the format(s) the rules apply to (ie, - * 'oas2', 'oas3') - */ - [index: string]: IRuleDeclaration; -} - -export interface IRuleDeclaration { - /** - * Name of the rule with either a rule definition (when definining/overriding - * rules) or boolean (when enabling/disabling a default rule) - */ - [ruleName: string]: Rule | boolean; -} - export * from './rule'; export * from './enums'; +export * from './spectral'; +export * from './function'; diff --git a/src/types/rule.ts b/src/types/rule.ts index 82cf8c364..1e3b1a741 100644 --- a/src/types/rule.ts +++ b/src/types/rule.ts @@ -3,23 +3,23 @@ import { RuleFunction, RuleType } from './enums'; export type Rule = | IRule - | ITruthyRule + | TruthyRule | IOrRule | IXorRule | IMaxLengthRule - | IAlphaRule + | AlphaRule | INotEndWithRule | INotContainRule | IPatternRule | ISchemaRule | IParamCheckRule; -export interface IRule { - type: RuleType; - +export interface IRule { // A short summary of the rule and its intended purpose summary: string; + type?: RuleType; + // The severity of results this rule generates severity?: ValidationSeverity; severityLabel?: ValidationSeverityLabel; @@ -45,20 +45,19 @@ export interface IRule { pattern?: string; }; - then: { - // the `path.to.prop` to field, or special `@key` value to target keys for matched `given` object - // EXAMPLE: if the target object is an oas object and given = `$..responses[*]`, then `@key` would be the response code (200, 400, etc) - field?: string; + then: IThen | Array>; +} - // a regex pattern - pattern?: string; +export interface IThen { + // the `path.to.prop` to field, or special `@key` value to target keys for matched `given` object + // EXAMPLE: if the target object is an oas object and given = `$..responses[*]`, then `@key` would be the response code (200, 400, etc) + field?: string; - // name of the function to run - function: string; + // name of the function to run + function: T; - // Options passed to the function - functionOptions?: O; - }; + // Options passed to the function + functionOptions?: O; } export interface IRuleParam { @@ -74,11 +73,6 @@ export interface IRuleNumberParam { property?: string; } -export interface IAlphaRuleParam extends IRuleParam { - // if sorting objects, use key for comparison - keyedBy?: string; -} - export interface IRulePatternParam { // value to use for rule value: string; @@ -93,17 +87,11 @@ export interface IRulePatternParam { split?: string; } -export interface ITruthyRule extends IRule { - function: RuleFunction.TRUTHY; - - functionOptions: { - // key(s) of object that should evaluate as 'truthy' (considered true in a - // boolean context) - properties: string | string[]; - - max?: number; - }; +export interface ITruthRuleOptions { + /** key(s) of object that should evaluate as 'truthy' (considered true in a boolean context) */ + properties: string | string[]; } +export type TruthyRule = IRule; export interface IOrRule extends IRule { then: { @@ -137,14 +125,11 @@ export interface IMaxLengthRule extends IRule { }; } -export interface IAlphaRule extends IRule { - then: { - function: RuleFunction.ALPHABETICAL; - - // verify property is within alphabetical order - functionOptions: IAlphaRuleParam; - }; +export interface IAlphaRuleOptions { + /** if sorting objects, use key for comparison */ + keyedBy?: string; } +export type AlphaRule = IRule; export interface INotEndWithRule extends IRule { then: { diff --git a/src/types/spectral.ts b/src/types/spectral.ts index 2749f9a85..4e4c89a72 100644 --- a/src/types/spectral.ts +++ b/src/types/spectral.ts @@ -1,21 +1,22 @@ -import { ObjPath } from '@stoplight/types/parsers'; -import { IRuleFunction, IRuleResult, Rule, RuleType } from '.'; +import { Dictionary, ObjPath } from '@stoplight/types'; -export interface IFunctionCollection { - [name: string]: IRuleFunction; -} +import { IFunction } from './function'; +import { IRule, Rule } from './rule'; -export interface IRuleCollection { - [index: string]: IRuleEntry; -} +export type FunctionCollection = Dictionary; +export type RuleCollection = Dictionary; +export type RunRuleCollection = Dictionary; -export interface IRuleEntry { +export interface IRunRule extends IRule { name: string; - format: string; - rule: Rule; - apply: IRuleFunction; } +/** + * Name of the rule with either a rule definition (when definining/overriding + * rules) or boolean (when enabling/disabling a default rule) + */ +export type RuleDeclaration = Dictionary | boolean, string>; + export interface IRunOpts { /** * The fully-resolved version of the target object. @@ -23,41 +24,13 @@ export interface IRunOpts { * Some functions require this in order to operate. */ resolvedTarget?: object; - - /** - * Optional rule type, when supplied only rules of this type are run - */ - type?: RuleType; - - /** - * The specification to apply to the target - */ - format: string; } -export type IFunction = ( - targetValue: any, - options: O, - paths: IFunctionPaths, - otherValues: IFunctionValues -) => void | IFunctionResult[]; - -export interface IFunctionPaths { - given: ObjPath; - target?: ObjPath; -} - -export interface IFunctionValues { - original: any; - resolved?: any; - given: any; +export interface IRunResult { + results: IRuleResult[]; } -export interface IFunctionResult { +export interface IRuleResult { + path: ObjPath; message: string; - path?: ObjPath; -} - -export interface IRunResult { - results: IRuleResult[]; } diff --git a/yarn.lock b/yarn.lock index 08ba7d726..7ac9390af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,18 +9,18 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/core@7.1.x": - version "7.1.6" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.1.6.tgz#3733cbee4317429bc87c62b29cf8587dba7baeb3" - integrity sha512-Hz6PJT6e44iUNpAn8AoyAs6B3bl60g7MJQaI0rZEar6ECzh6+srYO1xlIdssio34mPaUtAb1y+XlkkSJzok3yw== +"@babel/core@7.2.x": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.2.0.tgz#a4dd3814901998e93340f0086e9867fefa163ada" + integrity sha512-7pvAdC4B+iKjFFp9Ztj0QgBndJ++qaMeonT185wAqUnhipw8idm9Rv1UMyBuKtYjfl6ORNkgEgcsYLfHX/GpLw== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.1.6" - "@babel/helpers" "^7.1.5" - "@babel/parser" "^7.1.6" + "@babel/generator" "^7.2.0" + "@babel/helpers" "^7.2.0" + "@babel/parser" "^7.2.0" "@babel/template" "^7.1.2" "@babel/traverse" "^7.1.6" - "@babel/types" "^7.1.6" + "@babel/types" "^7.2.0" convert-source-map "^1.1.0" debug "^4.1.0" json5 "^2.1.0" @@ -29,7 +29,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.1.6": +"@babel/generator@^7.1.6", "@babel/generator@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.2.0.tgz#eaf3821fa0301d9d4aef88e63d4bcc19b73ba16c" integrity sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg== @@ -214,7 +214,7 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.2.0" -"@babel/helpers@^7.1.5": +"@babel/helpers@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.2.0.tgz#8335f3140f3144270dc63c4732a4f8b0a50b7a21" integrity sha512-Fr07N+ea0dMcMN8nFpuK6dUIT7/ivt9yKQdEEnjVS83tG2pHwPi03gYmk/tyuwONnZ+sY+GFFPlWGgCtW1hF9A== @@ -232,7 +232,7 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.1.2", "@babel/parser@^7.1.3", "@babel/parser@^7.1.6": +"@babel/parser@^7.1.2", "@babel/parser@^7.1.3", "@babel/parser@^7.1.6", "@babel/parser@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.2.0.tgz#02d01dbc330b6cbf36b76ac93c50752c69027065" integrity sha512-M74+GvK4hn1eejD9lZ7967qAwvqTZayQa3g10ag4s9uewgR7TKjeaT0YMyoq+gVfKYABiWZ4MQD701/t5e1Jhg== @@ -1117,7 +1117,7 @@ micromatch "^3.1.4" p-reduce "^1.0.0" -"@semantic-release/github@5.2.x", "@semantic-release/github@^5.1.0": +"@semantic-release/github@5.2.5", "@semantic-release/github@^5.1.0": version "5.2.5" resolved "https://registry.yarnpkg.com/@semantic-release/github/-/github-5.2.5.tgz#14e53d99f1e84c76b5674b7506f235a7a4cae302" integrity sha512-myO00q84CyfyzaEZ4OdA7GOMCQKd+juZd5g2Cloh4jV6CyiMyWflZ629RH99wjAVUiwMKnvX2SQ5XPFvO1+FCw== @@ -1172,12 +1172,12 @@ into-stream "^4.0.0" lodash "^4.17.4" -"@stoplight/scripts@3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@stoplight/scripts/-/scripts-3.0.2.tgz#f79b66ac4b4e6eae90f79f7e632926400ee72339" - integrity sha512-SIUJAS9HSEiwgWJQbZCSSWFjiXFTvvc/r7CHchq8P6ZhQ/UOS8ynVH4ZrKdoIqfxd2aySc2Km5eolccXmGCxDw== +"@stoplight/scripts@3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@stoplight/scripts/-/scripts-3.1.0.tgz#212f183ad2fc941be2c8e79d6abc40bfbb6e1500" + integrity sha512-TmF41NDj1X5EeNZeKyD2XoUdEeNa/i465xIwtmsDl3xBrmoPAJrm9mE5guOANGGqMj11SMxogt+X2BqUxGIveg== dependencies: - "@babel/core" "7.1.x" + "@babel/core" "7.2.x" "@commitlint/cli" "7.2.x" "@commitlint/config-conventional" "7.1.x" "@oclif/command" "1.5.x" @@ -1185,45 +1185,45 @@ "@oclif/plugin-help" "2.1.x" "@semantic-release/commit-analyzer" "6.1.x" "@semantic-release/git" "7.0.x" - "@semantic-release/github" "5.2.x" + "@semantic-release/github" "5.2.5" "@semantic-release/npm" "5.1.x" "@semantic-release/release-notes-generator" "7.1.x" - "@storybook/addon-actions" "4.0.7" - "@storybook/addon-info" "4.0.7" - "@storybook/addon-knobs" "4.0.7" - "@storybook/addon-links" "4.0.7" - "@storybook/addon-options" "4.0.7" - "@storybook/addons" "4.0.7" - "@storybook/core" "4.0.7" - "@storybook/react" "4.0.7" + "@storybook/addon-actions" "4.0.11" + "@storybook/addon-info" "4.0.11" + "@storybook/addon-knobs" "4.0.11" + "@storybook/addon-links" "4.0.11" + "@storybook/addon-options" "4.0.11" + "@storybook/addons" "4.0.11" + "@storybook/core" "4.0.11" + "@storybook/react" "4.0.11" "@types/enzyme" "3.1.x" - "@types/jest" "23.3.x" - "@types/node" "10.12.x" + "@types/jest" "23.3.10" + "@types/node" "10.12.12" "@types/storybook__addon-actions" "3.4.1" "@types/storybook__addon-info" "3.4.2" "@types/storybook__addon-knobs" "^3.4.1" "@types/storybook__addon-links" "3.3.3" - "@types/storybook__addon-options" "3.2.2" + "@types/storybook__addon-options" "3.2.3" "@types/storybook__react" "4.0.0" babel-loader "8.0.x" cli-ux "4.9.x" - commitizen "3.0.x" + commitizen "3.0.5" cz-conventional-changelog "2.1.x" enzyme "3.7.x" enzyme-adapter-react-16 "1.7.x" enzyme-to-json "3.3.x" - husky "1.1.x" + husky "1.2.0" inquirer "6.2.x" jest "23.6.x" jest-enzyme "7.0.x" - lint-staged "8.0.x" + lint-staged "8.1.0" lodash "4.x.x" react-docgen-typescript-loader "3.0.x" rimraf "2.6.x" - semantic-release "15.12.x" + semantic-release "15.12.4" shelljs "0.8.x" - ts-jest "23.10.x" - ts-loader "5.3.x" + ts-jest "23.10.5" + ts-loader "5.3.1" tslib "1.9.3" tslint-config-stoplight "1.2.x" typedoc "0.13.x" @@ -1234,17 +1234,17 @@ resolved "https://registry.yarnpkg.com/@stoplight/types/-/types-3.0.0.tgz#2464b3149accd0ef4b822a6079a1a8ece416e9c3" integrity sha512-08VUdzyHUenrk5m8rP6DiCQjZ/6DIjmFDKryBTuBNCrQQ6dwlqqGRN+f4nCOPc+Ks6jhHRLjT3hUCn8TUwIKuw== -"@storybook/addon-actions@4.0.7": - version "4.0.7" - resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-4.0.7.tgz#a7088e619f705f1839156fe9eec65e43e747d206" - integrity sha512-dUxFRDgn3RjvVlDQkLfJf43um90KFrzUzrdO7u+oQCtEUCmbD1bfI1waPyz+aV4RxfQIRUdMwkQaNKmAiqXBQg== +"@storybook/addon-actions@4.0.11": + version "4.0.11" + resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-4.0.11.tgz#4a329172baa8dc75a79af1dab72ed57ca2993440" + integrity sha512-YwJC3xfZP+CSG7M1AlnGlXQ8Mn9WbzMvgh1sh6CsvuRTKc9mOVAuWSdxkVw0cnfu1g44Ltb3MZ52+l/UFtTgkA== dependencies: "@emotion/core" "^0.13.1" "@emotion/provider" "^0.11.2" "@emotion/styled" "^0.10.6" - "@storybook/addons" "4.0.7" - "@storybook/components" "4.0.7" - "@storybook/core-events" "4.0.7" + "@storybook/addons" "4.0.11" + "@storybook/components" "4.0.11" + "@storybook/core-events" "4.0.11" deep-equal "^1.0.1" global "^4.3.2" lodash "^4.17.11" @@ -1253,15 +1253,15 @@ react-inspector "^2.3.0" uuid "^3.3.2" -"@storybook/addon-info@4.0.7": - version "4.0.7" - resolved "https://registry.yarnpkg.com/@storybook/addon-info/-/addon-info-4.0.7.tgz#10af6e94795eefabee8d331fcec8a66a22135b6b" - integrity sha512-3OciEPtkReWcA0cdDd1ZJ2ZQeovRGc7M/jV61pAO3ncAF/iHVnvX5+Zx9pX8c2qB49FOHZm++6DVpuJ3etbfFw== +"@storybook/addon-info@4.0.11": + version "4.0.11" + resolved "https://registry.yarnpkg.com/@storybook/addon-info/-/addon-info-4.0.11.tgz#d2312c89651d61456ab4f1a44c50a120fd91119b" + integrity sha512-8jX5hI8vrjxphddsKsSi/tGQY4Dfb+nlavfSeR1QEaIFUcTngxhbvGLnz4ZVeddIoRDFWeD9ls0zb3jmaDFtbA== dependencies: "@emotion/styled" "^0.10.6" - "@storybook/addons" "4.0.7" - "@storybook/client-logger" "4.0.7" - "@storybook/components" "4.0.7" + "@storybook/addons" "4.0.11" + "@storybook/client-logger" "4.0.11" + "@storybook/components" "4.0.11" core-js "2.5.7" global "^4.3.2" marksy "^6.1.0" @@ -1271,15 +1271,15 @@ react-lifecycles-compat "^3.0.4" util-deprecate "^1.0.2" -"@storybook/addon-knobs@4.0.7": - version "4.0.7" - resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-4.0.7.tgz#079d7708a2d4229be26089f3ac6e78aa02e49178" - integrity sha512-ye366yZf8PcUxIC226xI6tXdepKawp7hpsFNgEntXemqdjWewRv9mTd6YhrRoSNrLhGYNLMfHzYEAOFbJ0Ec4g== +"@storybook/addon-knobs@4.0.11": + version "4.0.11" + resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-4.0.11.tgz#14f0de5476e5429dbc745eb8d97d67d73497b937" + integrity sha512-bCWhUjqIzEoQ2YfLIE7pe16WpJ7sCf4P+HrqOyE7Cko/xo62S/jfFjuuspNbs6gB4jKyG/WAXdiS4BtxPMj1ZQ== dependencies: "@emotion/styled" "^0.10.6" - "@storybook/addons" "4.0.7" - "@storybook/components" "4.0.7" - "@storybook/core-events" "4.0.7" + "@storybook/addons" "4.0.11" + "@storybook/components" "4.0.11" + "@storybook/core-events" "4.0.11" copy-to-clipboard "^3.0.8" escape-html "^1.0.3" fast-deep-equal "^2.0.1" @@ -1290,58 +1290,73 @@ react-lifecycles-compat "^3.0.4" util-deprecate "^1.0.2" -"@storybook/addon-links@4.0.7": - version "4.0.7" - resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-4.0.7.tgz#2579707052cfc9bcb2a97d21c7363c673574ae89" - integrity sha512-1MTWD+2Qu5txSxPykk7le8gEA66Yjm+USDC1Gbq0KqSLoQ0vh2Fxd6cXE+mIUBC2IIIqDio2SjkqV0974B8JgA== +"@storybook/addon-links@4.0.11": + version "4.0.11" + resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-4.0.11.tgz#2cc0e1e859febf0623b71e07ff9d3503fc212c9e" + integrity sha512-iVRpn7UxBI94SIEP5dTAvjNGYtZXwVRItAK0YxBbyOB+iafDqwpuMUweVmGMmL9jw4VsICu9xHRPCKxWr2Ckqw== dependencies: - "@storybook/addons" "4.0.7" - "@storybook/components" "4.0.7" - "@storybook/core-events" "4.0.7" + "@storybook/addons" "4.0.11" + "@storybook/components" "4.0.11" + "@storybook/core-events" "4.0.11" global "^4.3.2" prop-types "^15.6.2" -"@storybook/addon-options@4.0.7": - version "4.0.7" - resolved "https://registry.yarnpkg.com/@storybook/addon-options/-/addon-options-4.0.7.tgz#c7a7bb8dcfb4bc2a7c4ece56f96e0f65635cc747" - integrity sha512-69LY1/O2xiREJqpVNx4VzTHxkLkS0NeohtA05dTyHVpP+jYAAhtWOkwAoGUrOWF5L2IFS/o/oX6wcKLHxIr4qA== +"@storybook/addon-options@4.0.11": + version "4.0.11" + resolved "https://registry.yarnpkg.com/@storybook/addon-options/-/addon-options-4.0.11.tgz#22eba81942747a8e1dc7132eaffd1dad9f0a822a" + integrity sha512-W856fnhHENkzPMvB3XMEBrhF8Gn2JWAYiLnr22Sq5J/i2aDS5+APvDUrHSDOUgCqZJF07oaFpxqTQLbJfuB/Pg== dependencies: - "@storybook/addons" "4.0.7" + "@storybook/addons" "4.0.8" util-deprecate "^1.0.2" -"@storybook/addons@4.0.7": - version "4.0.7" - resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-4.0.7.tgz#52a98ebfa862b34ed47368590564a9638b86d211" - integrity sha512-rfumQnFLMhpGx3nvzhW+stTFKwp5SMOso7pryEBhxxskT4f6kBuzZyaChhnSBWBwVz5bPpplWj5l0H0F2/+5bg== +"@storybook/addons@4.0.11": + version "4.0.11" + resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-4.0.11.tgz#e02cb5084d65a0a6cfc4c5521031fde150b93e2f" + integrity sha512-b187r62kzYh2WPW3aa1BVP/YvKUqh1xIUFQx8Pw8iJ7Uo5evexkI7utiu2KzWk4hmn0IBavdDshM+NykOjWM7A== dependencies: - "@storybook/channels" "4.0.7" - "@storybook/components" "4.0.7" + "@storybook/channels" "4.0.11" + "@storybook/components" "4.0.11" global "^4.3.2" util-deprecate "^1.0.2" -"@storybook/channel-postmessage@4.0.7": - version "4.0.7" - resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-4.0.7.tgz#70dafdaabd8431ca1a042c8512a29ca73f9009eb" - integrity sha512-Ya9D8dCKB8dqRwmIFS6IDOmNJX1TX3my9KmSRociyV1aku5kIDmMbdKHKMAAEDK4H1CmkAglW90ePsHCcVAkRw== +"@storybook/addons@4.0.8": + version "4.0.8" + resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-4.0.8.tgz#841848c57406296f7b513708c15dc865f6b37fc7" + integrity sha512-rcLqzRHFPYQ5S0/NKc3Qk7klUseiq3YCAKghYjGzXPENgID6rKnqAYrlOybPgR0aUWs6koXKiIjtco9tT1UBxQ== dependencies: - "@storybook/channels" "4.0.7" + "@storybook/channels" "4.0.8" + "@storybook/components" "4.0.8" + global "^4.3.2" + util-deprecate "^1.0.2" + +"@storybook/channel-postmessage@4.0.11": + version "4.0.11" + resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-4.0.11.tgz#45b4e522a32b51682e5a1105a3d1c16b6fb366df" + integrity sha512-adk4Ox93+cXC0Wkjo+1ANV26/FENTH52AZxCnwvtjEPiOCnZi7+qUgGj5rSZX5zkIHJV8dHeiXWwh4sgFqeWhA== + dependencies: + "@storybook/channels" "4.0.11" global "^4.3.2" json-stringify-safe "^5.0.1" -"@storybook/channels@4.0.7": - version "4.0.7" - resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-4.0.7.tgz#bb7607c9d0036cbed884c11683d7c168c2663733" - integrity sha512-XSfMaD+GKQpNtHSAZb0aMjDH59rYIvTB3je0jM67NVHneIbZrxxEcY52QZ7KP2eAP9qRw3yK89VoNo7D0APdcQ== +"@storybook/channels@4.0.11": + version "4.0.11" + resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-4.0.11.tgz#e6303959364d6d66918dbcbafe0c64df1c5b79d3" + integrity sha512-Rewcss3OW1gjgURNVEb4/NSrKGL/huJ+r/PxdXgi81AX1cafj+fMHRbL9AaG5/SNtPDY/6EfIGlC0uGCUbyVOw== -"@storybook/client-logger@4.0.7": - version "4.0.7" - resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-4.0.7.tgz#6831c35959a8d3d06c381c0068bffecbe0260444" - integrity sha512-tStgsg6HlZDgP38o97i5fkCDtLZAV9SUYE6of2AJUL7yf+yfL7Hh3pkIeXK4AsxAEnbhkPmCn07bCHD1NvxE4g== +"@storybook/channels@4.0.8": + version "4.0.8" + resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-4.0.8.tgz#36d96ad714c685f144fd1f1e5394faf0e0291032" + integrity sha512-nGMVPN4eOJlAXxS2u3a5aj7YJ4ChhlLx0+4HC6xaU5Uv+PaL6ba0gh84Zd9K73JnTSPPMq/4Mg3oncZumyeN9Q== -"@storybook/components@4.0.7": - version "4.0.7" - resolved "https://registry.yarnpkg.com/@storybook/components/-/components-4.0.7.tgz#f5a51b6999df7bd137a02a968d17795c836bfa5b" - integrity sha512-edQdAx+Vy6ycg6wxj5vX2DzDIcH9hikj517wKunlv78zv+82aFgHI/UZoUhaSL2dqYWOx+dO1eSza5456y7llQ== +"@storybook/client-logger@4.0.11": + version "4.0.11" + resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-4.0.11.tgz#955419cc13c8a77f14bd804adbb601c78b7b0128" + integrity sha512-DwPTGqkl18P1MlL+4d9tZTh/Urj2eor/0RJMmt3iy54SBRcwn7QoG14Pdsg3xVE7t9ojeKnBPQOYgsfa7J3SrQ== + +"@storybook/components@4.0.11": + version "4.0.11" + resolved "https://registry.yarnpkg.com/@storybook/components/-/components-4.0.11.tgz#a09a22120685e324831876a620112ca16a28bdcb" + integrity sha512-oAy9NqCgq9gJeQ+YO+IXHUq9rqmnUxGae+jbM8lGJlBS0yKx1vpOsZYO/jV3qhNPcGWZzKVxQdoWH5ej4sq5TA== dependencies: "@emotion/core" "^0.13.1" "@emotion/provider" "^0.11.2" @@ -1354,15 +1369,31 @@ react-textarea-autosize "^7.0.4" render-fragment "^0.1.1" -"@storybook/core-events@4.0.7": - version "4.0.7" - resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-4.0.7.tgz#8fb7be5dd0fba5c6928b15cd2d92ffdb0a53da6f" - integrity sha512-rirZ/dtcA22BqZoxsYGeUHdS0IQFKNerTnNcDuL7NaTD79tl1JaWYvtLot3GqxLD7/lyUh0GQIGnxqr1ouGBhQ== +"@storybook/components@4.0.8": + version "4.0.8" + resolved "https://registry.yarnpkg.com/@storybook/components/-/components-4.0.8.tgz#0c0701e64b52538acb0d39412db476549a3d7579" + integrity sha512-6r6/B2i2sQrDPvOgzo5YgCtSCj3q4E54e6V5fkbrYhSvH7Ro8fwca6Pee0uHbKECT+N8+HzWveV0WKg20TZvIg== + dependencies: + "@emotion/core" "^0.13.1" + "@emotion/provider" "^0.11.2" + "@emotion/styled" "^0.10.6" + global "^4.3.2" + lodash "^4.17.11" + prop-types "^15.6.2" + react-inspector "^2.3.0" + react-split-pane "^0.1.84" + react-textarea-autosize "^7.0.4" + render-fragment "^0.1.1" + +"@storybook/core-events@4.0.11": + version "4.0.11" + resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-4.0.11.tgz#f524a3611e40baade2c5bacb309d37a6af9d952b" + integrity sha512-SHOyEeKa3Nb2RSaDChwpebZMkAlyEM/TkUOjhQUKFvwirXEzhlKg5O9JrNxEAkH5zZbtXNzYY4Vod3c+Fv03uA== -"@storybook/core@4.0.7": - version "4.0.7" - resolved "https://registry.yarnpkg.com/@storybook/core/-/core-4.0.7.tgz#5242ae96ebe207401d243f245649d073ecf2b30b" - integrity sha512-DHg1E1UNGHVCcB/gkScEfn/L4AtSEaO9UzJNpP+jefyleY7IPu0eeR4Cg6YbRx01BfItIE8swNA4y/HT94xmLQ== +"@storybook/core@4.0.11": + version "4.0.11" + resolved "https://registry.yarnpkg.com/@storybook/core/-/core-4.0.11.tgz#40eb2861a8fdfbcb8071ba3fb2c1a509c12c36c3" + integrity sha512-/uo+yang8joX5uZeQWQel5dPMZzZg7VeIvMAfTtxCHyWOpltIgUg/Yb57GwoT2iQ+8sjxiurJjTW29XpNL1LRg== dependencies: "@babel/plugin-proposal-class-properties" "^7.1.0" "@babel/plugin-transform-regenerator" "^7.0.0" @@ -1372,12 +1403,12 @@ "@emotion/core" "^0.13.1" "@emotion/provider" "^0.11.2" "@emotion/styled" "^0.10.6" - "@storybook/addons" "4.0.7" - "@storybook/channel-postmessage" "4.0.7" - "@storybook/client-logger" "4.0.7" - "@storybook/core-events" "4.0.7" - "@storybook/node-logger" "4.0.7" - "@storybook/ui" "4.0.7" + "@storybook/addons" "4.0.11" + "@storybook/channel-postmessage" "4.0.11" + "@storybook/client-logger" "4.0.11" + "@storybook/core-events" "4.0.11" + "@storybook/node-logger" "4.0.11" + "@storybook/ui" "4.0.11" airbnb-js-shims "^1 || ^2" autoprefixer "^9.3.1" babel-plugin-macros "^2.4.2" @@ -1419,6 +1450,7 @@ shelljs "^0.8.2" style-loader "^0.23.1" svg-url-loader "^2.3.2" + terser-webpack-plugin "^1.1.0" url-loader "^1.1.2" webpack "^4.23.1" webpack-dev-middleware "^3.4.0" @@ -1433,10 +1465,10 @@ "@storybook/react-simple-di" "^1.2.1" babel-runtime "6.x.x" -"@storybook/node-logger@4.0.7": - version "4.0.7" - resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-4.0.7.tgz#aa57636d6d46b446319b06daceda562b7b78880a" - integrity sha512-RIT0fBPVvHbvvRJ7W5NwtiT3kQxm1522Fn7/0Mf4ejUerQNtqvbosTJhN2ReinavlD4Fo85QXFFCmZ2RXMiBVw== +"@storybook/node-logger@4.0.11": + version "4.0.11" + resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-4.0.11.tgz#8ab0caaa682065bf9631b134cf7cad841483982f" + integrity sha512-PpXESCRMbja+5BGyEBvcBL35XX9mrLA6CEQYLmqw7u1FLEFIcWgYZL33o5tSi2+0NsHZKvakRxsGw8DGcfxhpQ== dependencies: "@babel/runtime" "^7.1.2" npmlog "^4.1.2" @@ -1477,17 +1509,17 @@ dependencies: babel-runtime "^6.5.0" -"@storybook/react@4.0.7": - version "4.0.7" - resolved "https://registry.yarnpkg.com/@storybook/react/-/react-4.0.7.tgz#2522d8228f5754a42606cb8d9fcf2f20444e2e02" - integrity sha512-SgvcavdNhIQbMerqCtkWU/MO6UhumaJ3DNWZsnA71Sw43qtPVrwqQekGJuERkL7R9uI0pWbMSCNh4RC9e20/Jg== +"@storybook/react@4.0.11": + version "4.0.11" + resolved "https://registry.yarnpkg.com/@storybook/react/-/react-4.0.11.tgz#2e7d93797ffcad518c82533c3ee1b7aa2c287dc6" + integrity sha512-HP2XlRQijL4ig5+Cp2YdxEgMxVPkZfy4lUwg8YXbwQpFJhAAVBBiyfd/iDj1zYeniBaZd6S9FJRMWHN0zX8oug== dependencies: "@babel/preset-flow" "^7.0.0" "@babel/preset-react" "^7.0.0" "@babel/runtime" "^7.1.2" "@emotion/styled" "^0.10.6" - "@storybook/core" "4.0.7" - "@storybook/node-logger" "4.0.7" + "@storybook/core" "4.0.11" + "@storybook/node-logger" "4.0.11" babel-plugin-react-docgen "^2.0.0" common-tags "^1.8.0" global "^4.3.2" @@ -1498,16 +1530,16 @@ semver "^5.6.0" webpack "^4.23.1" -"@storybook/ui@4.0.7": - version "4.0.7" - resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-4.0.7.tgz#ac6f66c7973ff19f0c0f27fd5bca2856eed628cd" - integrity sha512-VSvSU0Ac81D0J26U6+E0zSQBA7VtjVFBEv/SwXJ81DxA8jgCK08YZF8CGaTJGEcY6EY3G1HACcfnVHrXyzT8zw== +"@storybook/ui@4.0.11": + version "4.0.11" + resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-4.0.11.tgz#20f85b159c9f42106a21c1184eef257eadde7ed4" + integrity sha512-gvIJB9shQoIe0/WWuXQSWElkC4Ho4DKgxRq2QV72kUIRZKnlAKC6ufOc40MhbNwIkvNWG9slPzbLn1NJTmL0eg== dependencies: "@emotion/core" "^0.13.1" "@emotion/provider" "^0.11.2" "@emotion/styled" "^0.10.6" - "@storybook/components" "4.0.7" - "@storybook/core-events" "4.0.7" + "@storybook/components" "4.0.11" + "@storybook/core-events" "4.0.11" "@storybook/mantra-core" "^1.7.2" "@storybook/podda" "^1.2.3" "@storybook/react-komposer" "^2.0.5" @@ -1568,7 +1600,7 @@ resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.3.tgz#b672cfaac25cbbc634a0fd92c515f66faa18dbca" integrity sha512-pGF/zvYOACZ/gLGWdQH8zSwteQS1epp68yRcVLJMgUck/MjEn/FBYmPub9pXT8C1e4a8YZfHo1CKyV8q1vKUnQ== -"@types/jest@23.3.x": +"@types/jest@23.3.10": version "23.3.10" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.10.tgz#4897974cc317bf99d4fe6af1efa15957fa9c94de" integrity sha512-DC8xTuW/6TYgvEg3HEXS7cu9OijFqprVDXXiOcdOKZCU/5PJNLZU37VVvmZHdtMiGOa8wAA/We+JzbdxFzQTRQ== @@ -1593,7 +1625,7 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== -"@types/node@*", "@types/node@10.12.x": +"@types/node@*", "@types/node@10.12.12": version "10.12.12" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.12.tgz#e15a9d034d9210f00320ef718a50c4a799417c47" integrity sha512-Pr+6JRiKkfsFvmU/LK68oBRCQeEg36TyAbPhc2xpez24OOZZCuoIhWGTd39VZy6nGafSbxzGouFPTFD/rR1A0A== @@ -1645,12 +1677,14 @@ dependencies: "@types/react" "*" -"@types/storybook__addon-options@3.2.2": - version "3.2.2" - resolved "https://registry.yarnpkg.com/@types/storybook__addon-options/-/storybook__addon-options-3.2.2.tgz#f42f81414fa9692cf20d947e9b49c60c4bdfbc4d" - integrity sha512-QYWklZ37+QOcjwuolrvb121oXR6kA+5ucg9ieb2w0J0lATUp8DeQ804oGvf3GUXzJPzJiTnbhcX/aE+F1vvxGQ== +"@types/storybook__addon-options@3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@types/storybook__addon-options/-/storybook__addon-options-3.2.3.tgz#a33649175959b3703d8a9996d8120190d3674ab0" + integrity sha512-HcQfDBWL5Lb7NX1UNfWE3gk+0n2n8bsvYd17q9LUjaQB+MuHWeKmHI60zGFL/3Jw1wo9qOTeupVLRGh0x+d6fw== + dependencies: + "@types/storybook__react" "*" -"@types/storybook__react@4.0.0": +"@types/storybook__react@*", "@types/storybook__react@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/storybook__react/-/storybook__react-4.0.0.tgz#52cc452fbab599568d595075a90142ef4a1233f6" integrity sha512-Iq3RX953fqZRwWN3jywm8pUx1/Atev+x/9tF7/2CNA+Ii55sGSJJRWMRthUKQXTa3zOexcvfksfVYdUaIZY91w== @@ -3487,7 +3521,7 @@ commander@^2.12.1, commander@^2.14.1, commander@^2.19.0, commander@^2.9.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== -commitizen@3.0.x: +commitizen@3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/commitizen/-/commitizen-3.0.5.tgz#607e07a6d3f2aa201b91a51607dc4d337c84a0ea" integrity sha512-WB9sz7qudArOsW1ninU8YGLNoXLQ5lJBZf538iQ7i96SXAkqVMZdmPtSyN4WFPM5PjQR7rWxDa+hzfGIJfrXUg== @@ -3718,6 +3752,15 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= +cosmiconfig@5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.0.6.tgz#dca6cf680a0bd03589aff684700858c81abeeb39" + integrity sha512-6DWfizHriCrFWURP1/qyhsiFvYdlJzbCzmtFWh744+KyWsJo5+kPzUZZaMRSSItoYc0pxFX7gEO7ZC1/gN/7AQ== + dependencies: + is-directory "^0.3.1" + js-yaml "^3.9.0" + parse-json "^4.0.0" + cosmiconfig@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-4.0.0.tgz#760391549580bbd2df1e562bc177b13c290972dc" @@ -3728,7 +3771,7 @@ cosmiconfig@^4.0.0: parse-json "^4.0.0" require-from-string "^2.0.1" -cosmiconfig@^5.0.1, cosmiconfig@^5.0.2, cosmiconfig@^5.0.5, cosmiconfig@^5.0.6: +cosmiconfig@^5.0.1, cosmiconfig@^5.0.5, cosmiconfig@^5.0.6: version "5.0.7" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.0.7.tgz#39826b292ee0d78eda137dfa3173bd1c21a43b04" integrity sha512-PcLqxTKiDmNT6pSpy4N6KtuPwb53W+2tzNvwOZw0WH9N6O0vLIBq0x8aj8Oj75ere4YcGi48bDFCL+3fRJdlNA== @@ -5899,10 +5942,10 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -husky@1.1.x: - version "1.1.4" - resolved "https://registry.yarnpkg.com/husky/-/husky-1.1.4.tgz#92f61383527d2571e9586234e5864356bfaceaa9" - integrity sha512-cZjGpS7qsaBSo3fOMUuR7erQloX3l5XzL1v/RkIqU6zrQImDdU70z5Re9fGDp7+kbYlM2EtS4aYMlahBeiCUGw== +husky@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/husky/-/husky-1.2.0.tgz#d631dda1e4a9ee8ba69a10b0c51a0e2c66e711e5" + integrity sha512-/ib3+iycykXC0tYIxsyqierikVa9DA2DrT32UEirqNEFVqOj1bFMTgP3jAz8HM7FgC/C8pc/BTUa9MV2GEkZaA== dependencies: cosmiconfig "^5.0.6" execa "^1.0.0" @@ -7357,15 +7400,15 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= -lint-staged@8.0.x: - version "8.0.5" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-8.0.5.tgz#8eeca1a213eaded09c4e217da455b6f432046034" - integrity sha512-QI2D6lw2teArlr2fmrrCIqHxef7mK2lKjz9e+aZSzFlk5rsy10rg97p3wA9H/vIFR3Fvn34fAgUktD/k896S2A== +lint-staged@8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-8.1.0.tgz#dbc3ae2565366d8f20efb9f9799d076da64863f2" + integrity sha512-yfSkyJy7EuVsaoxtUSEhrD81spdJOe/gMTGea3XaV7HyoRhTb9Gdlp6/JppRZERvKSEYXP9bjcmq6CA5oL2lYQ== dependencies: "@iamstarkov/listr-update-renderer" "0.4.1" chalk "^2.3.1" commander "^2.14.1" - cosmiconfig "^5.0.2" + cosmiconfig "5.0.6" debug "^3.1.0" dedent "^0.7.0" del "^3.0.0" @@ -10683,7 +10726,7 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -semantic-release@15.12.x: +semantic-release@15.12.4: version "15.12.4" resolved "https://registry.yarnpkg.com/semantic-release/-/semantic-release-15.12.4.tgz#d0f4c3649d01e2e29a9df5e4886bab869d75ef02" integrity sha512-po30Te9E26v3Qb/G9pXFO6lCTFO07zvliqH00vmfuCoAjl1Wpg9SKb9dXVFM7BTdg5Fr/KqbdOsqHkT7kR4FeQ== @@ -11744,7 +11787,7 @@ trim-right@^1.0.1: resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= -ts-jest@23.10.x: +ts-jest@23.10.5: version "23.10.5" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-23.10.5.tgz#cdb550df4466a30489bf70ba867615799f388dd5" integrity sha512-MRCs9qnGoyKgFc8adDEntAOP64fWK1vZKnOYU1o2HxaqjdJvGqmkLCPCnVq1/If4zkUmEjKPnCiUisTrlX2p2A== @@ -11759,7 +11802,7 @@ ts-jest@23.10.x: semver "^5.5" yargs-parser "10.x" -ts-loader@5.3.x: +ts-loader@5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-5.3.1.tgz#70614c8ec4354a9c8b89c9f97b2becb7a98a3980" integrity sha512-fDDgpBH3SR8xlt2MasLdz3Yy611PQ/UY/KGyo7TgXhTRU/6sS8uGG0nJYnU1OdFBNKcoYbId1UTNaAOUn+i41g== @@ -11899,10 +11942,10 @@ typescript@3.1.x: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.6.tgz#b6543a83cfc8c2befb3f4c8fba6896f5b0c9be68" integrity sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA== -typescript@3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.1.tgz#0b7a04b8cf3868188de914d9568bd030f0c56192" - integrity sha512-jw7P2z/h6aPT4AENXDGjcfHTu5CSqzsbZc6YlUIebTyBAq8XaKp78x7VcSh30xwSCcsu5irZkYZUSFP1MrAMbg== +typescript@3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.2.tgz#fe8101c46aa123f8353523ebdcf5730c2ae493e5" + integrity sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg== ua-parser-js@^0.7.18: version "0.7.19"