diff --git a/@commitlint/parse/package.json b/@commitlint/parse/package.json index 5af437cce1..774f031c00 100644 --- a/@commitlint/parse/package.json +++ b/@commitlint/parse/package.json @@ -3,35 +3,13 @@ "version": "8.1.0", "description": "Lint your commit messages", "main": "lib/index.js", + "types": "lib/index.d.ts", "files": [ "lib/" ], "scripts": { - "build": "cross-env NODE_ENV=production babel src --out-dir lib --source-maps", "deps": "dep-check", - "pkg": "pkg-check", - "start": "concurrently \"ava -c 4 --verbose --watch\" \"yarn run watch\"", - "test": "ava -c 4 --verbose", - "watch": "babel src --out-dir lib --watch --source-maps" - }, - "ava": { - "files": [ - "src/**/*.test.js", - "!lib/**/*" - ], - "source": [ - "src/**/*.js", - "!lib/**/*" - ], - "babel": "inherit", - "require": [ - "babel-register" - ] - }, - "babel": { - "presets": [ - "babel-preset-commitlint" - ] + "pkg": "pkg-check" }, "engines": { "node": ">=4" @@ -58,13 +36,9 @@ "devDependencies": { "@commitlint/test": "8.0.0", "@commitlint/utils": "^8.1.0", - "ava": "0.22.0", - "babel-cli": "6.26.0", - "babel-preset-commitlint": "^8.0.0", - "babel-register": "6.26.0", - "concurrently": "3.6.1", - "cross-env": "5.1.1", - "import-from": "3.0.0" + "@types/lodash": "^4.14.136", + "import-from": "3.0.0", + "typescript": "^3.5.3" }, "dependencies": { "conventional-changelog-angular": "^1.3.3", diff --git a/@commitlint/parse/src/index.js b/@commitlint/parse/src/index.js deleted file mode 100644 index ee4f7742ce..0000000000 --- a/@commitlint/parse/src/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import {sync} from 'conventional-commits-parser'; -import defaultChangelogOpts from 'conventional-changelog-angular'; -import {isArray, mergeWith} from 'lodash'; - -export default parse; - -async function parse(message, parser = sync, parserOpts = undefined) { - const defaultOpts = (await defaultChangelogOpts).parserOpts; - const parsed = parser( - message, - mergeWith({}, defaultOpts, parserOpts, (objValue, srcValue) => { - if (isArray(objValue)) return srcValue; - }) - ); - parsed.raw = message; - return parsed; -} diff --git a/@commitlint/parse/src/index.test.js b/@commitlint/parse/src/index.test.ts similarity index 59% rename from @commitlint/parse/src/index.test.js rename to @commitlint/parse/src/index.test.ts index f44a1182bb..5e0f980026 100644 --- a/@commitlint/parse/src/index.test.js +++ b/@commitlint/parse/src/index.test.ts @@ -1,40 +1,43 @@ import importFrom from 'import-from'; -import test from 'ava'; import parse from '.'; -test('throws when called without params', async t => { - const error = await t.throws(parse()); - t.is(error.message, 'Expected a raw commit'); +test('throws when called without params', () => { + expect((parse as any)()).rejects.toThrowError('Expected a raw commit'); }); -test('throws when called with empty message', async t => { - const error = await t.throws(parse()); - t.is(error.message, 'Expected a raw commit'); +test('throws when called with empty message', () => { + expect(parse('')).rejects.toThrowError('Expected a raw commit'); }); -test('returns object with raw message', async t => { +test('returns object with raw message', async () => { const message = 'type(scope): subject'; const actual = await parse(message); - t.is(actual.raw, message); + + expect(actual).toHaveProperty('raw', message); }); -test('calls parser with message and passed options', async t => { +test('calls parser with message and passed options', async () => { const message = 'message'; - await parse(message, m => { - t.is(message, m); - return {}; - }); + expect.assertions(1); + await parse( + message, + (m: string): any => { + expect(m).toBe(message); + return {}; + } + ); }); -test('passes object up from parser function', async t => { +test('passes object up from parser function', async () => { const message = 'message'; - const result = {}; + const result: any = {}; const actual = await parse(message, () => result); - t.is(actual, result); + + expect(actual).toBe(result); }); -test('returns object with expected keys', async t => { +test('returns object with expected keys', async () => { const message = 'message'; const actual = await parse(message); const expected = { @@ -51,10 +54,11 @@ test('returns object with expected keys', async t => { subject: null, type: null }; - t.deepEqual(actual, expected); + + expect(actual).toMatchObject(expected); }); -test('uses angular grammar', async t => { +test('uses angular grammar', async () => { const message = 'type(scope): subject'; const actual = await parse(message); const expected = { @@ -71,14 +75,15 @@ test('uses angular grammar', async t => { subject: 'subject', type: 'type' }; - t.deepEqual(actual, expected); + + expect(actual).toMatchObject(expected); }); -test('uses custom opts parser', async t => { +test('uses custom opts parser', async () => { const message = 'type(scope)-subject'; - const changelogOpts = await importFrom( - process.cwd(), - './fixtures/parser-preset/conventional-changelog-custom' + const changelogOpts: any = await importFrom( + __dirname, + '../fixtures/parser-preset/conventional-changelog-custom.js' ); const actual = await parse(message, undefined, changelogOpts.parserOpts); const expected = { @@ -95,10 +100,11 @@ test('uses custom opts parser', async t => { subject: 'subject', type: 'type' }; - t.deepEqual(actual, expected); + + expect(actual).toMatchObject(expected); }); -test('does not merge array properties with custom opts', async t => { +test('does not merge array properties with custom opts', async () => { const message = 'type: subject'; const actual = await parse(message, undefined, { headerPattern: /^(.*):\s(.*)$/, @@ -117,26 +123,29 @@ test('does not merge array properties with custom opts', async t => { subject: 'subject', type: 'type' }; - t.deepEqual(actual, expected); + + expect(actual).toMatchObject(expected); }); -test('supports scopes with /', async t => { +test('supports scopes with /', async () => { const message = 'type(some/scope): subject'; const actual = await parse(message); - t.is(actual.scope, 'some/scope'); - t.is(actual.subject, 'subject'); + + expect(actual.scope).toBe('some/scope'); + expect(actual.subject).toBe('subject'); }); -test('supports scopes with / and empty parserOpts', async t => { +test('supports scopes with / and empty parserOpts', async () => { const message = 'type(some/scope): subject'; const actual = await parse(message, undefined, {}); - t.is(actual.scope, 'some/scope'); - t.is(actual.subject, 'subject'); + + expect(actual.scope).toBe('some/scope'); + expect(actual.subject).toBe('subject'); }); -test('ignores comments', async t => { +test('ignores comments', async () => { const message = 'type(some/scope): subject\n# some comment'; - const changelogOpts = await importFrom( + const changelogOpts: any = await importFrom( process.cwd(), 'conventional-changelog-angular' ); @@ -144,15 +153,16 @@ test('ignores comments', async t => { commentChar: '#' }); const actual = await parse(message, undefined, opts); - t.is(actual.body, null); - t.is(actual.footer, null); - t.is(actual.subject, 'subject'); + + expect(actual.body).toBe(null); + expect(actual.footer).toBe(null); + expect(actual.subject).toBe('subject'); }); -test('registers inline #', async t => { +test('registers inline #', async () => { const message = 'type(some/scope): subject #reference\n# some comment\nthings #reference'; - const changelogOpts = await importFrom( + const changelogOpts: any = await importFrom( process.cwd(), 'conventional-changelog-angular' ); @@ -160,11 +170,12 @@ test('registers inline #', async t => { commentChar: '#' }); const actual = await parse(message, undefined, opts); - t.is(actual.subject, 'subject #reference'); - t.is(actual.body, 'things #reference'); + + expect(actual.subject).toBe('subject #reference'); + expect(actual.body).toBe('things #reference'); }); -test('parses references leading subject', async t => { +test('parses references leading subject', async () => { const message = '#1 some subject'; const opts = await importFrom( process.cwd(), @@ -172,18 +183,19 @@ test('parses references leading subject', async t => { ); const { references: [actual] - } = await parse(message, undefined, opts); - t.is(actual.issue, '1'); + } = await parse(message, undefined, opts as any); + + expect(actual.issue).toBe('1'); }); -test('parses custom references', async t => { +test('parses custom references', async () => { const message = '#1 some subject PREFIX-2'; const {references} = await parse(message, undefined, { issuePrefixes: ['PREFIX-'] }); - t.falsy(references.find(ref => ref.issue === '1')); - t.deepEqual(references.find(ref => ref.issue === '2'), { + expect(references.find(ref => ref.issue === '1')).toBeFalsy(); + expect(references.find(ref => ref.issue === '2')).toMatchObject({ action: null, issue: '2', owner: null, @@ -193,44 +205,44 @@ test('parses custom references', async t => { }); }); -test('uses permissive default regex without parser opts', async t => { +test('uses permissive default regex without parser opts', async () => { const message = 'chore(component,demo): bump'; const actual = await parse(message); - t.is(actual.scope, 'component,demo'); + expect(actual.scope).toBe('component,demo'); }); -test('uses permissive default regex with other parser opts', async t => { +test('uses permissive default regex with other parser opts', async () => { const message = 'chore(component,demo): bump'; const actual = await parse(message, undefined, {commentChar: '#'}); - t.is(actual.scope, 'component,demo'); + expect(actual.scope).toBe('component,demo'); }); -test('uses restrictive default regex in passed parser opts', async t => { +test('uses restrictive default regex in passed parser opts', async () => { const message = 'chore(component,demo): bump'; const actual = await parse(message, undefined, { headerPattern: /^(\w*)(?:\(([a-z]*)\))?: (.*)$/ }); - t.is(actual.subject, null); - t.is(actual.scope, null); + expect(actual.subject).toBe(null); + expect(actual.scope).toBe(null); }); -test('works with chinese scope by default', async t => { +test('works with chinese scope by default', async () => { const message = 'fix(面试评价): 测试'; const actual = await parse(message, undefined, {commentChar: '#'}); - t.not(actual.subject, null); - t.not(actual.scope, null); + expect(actual.subject).not.toBe(null); + expect(actual.scope).not.toBe(null); }); -test('does not work with chinese scopes with incompatible pattern', async t => { +test('does not work with chinese scopes with incompatible pattern', async () => { const message = 'fix(面试评价): 测试'; const actual = await parse(message, undefined, { headerPattern: /^(\w*)(?:\(([a-z]*)\))?: (.*)$/ }); - t.is(actual.subject, null); - t.is(actual.scope, null); + expect(actual.subject).toBe(null); + expect(actual.scope).toBe(null); }); diff --git a/@commitlint/parse/src/index.ts b/@commitlint/parse/src/index.ts new file mode 100644 index 0000000000..3f2a8fd72c --- /dev/null +++ b/@commitlint/parse/src/index.ts @@ -0,0 +1,22 @@ +import {isArray, mergeWith} from 'lodash'; +import {Commit, Parser, ParserOptions} from './types'; + +const {sync} = require('conventional-commits-parser'); +const defaultChangelogOpts = require('conventional-changelog-angular'); + +export default parse; +export * from './types'; + +async function parse( + message: string, + parser: Parser = sync, + parserOpts?: ParserOptions +): Promise { + const defaultOpts = (await defaultChangelogOpts).parserOpts; + const opts = mergeWith({}, defaultOpts, parserOpts, (objValue, srcValue) => { + if (isArray(objValue)) return srcValue; + }); + const parsed = parser(message, opts) as Commit; + parsed.raw = message; + return parsed; +} diff --git a/@commitlint/parse/src/types.ts b/@commitlint/parse/src/types.ts new file mode 100644 index 0000000000..2654086d66 --- /dev/null +++ b/@commitlint/parse/src/types.ts @@ -0,0 +1,45 @@ +export interface Commit { + raw: string; + header: string; + type: string | null; + scope: string | null; + subject: string | null; + body: string | null; + footer: string | null; + mentions: string[]; + notes: CommitNote[]; + references: CommitReference[]; + revert: any; + merge: any; +} + +export interface CommitNote { + title: string; + text: string; +} + +export interface CommitReference { + raw: string; + prefix: string; + action: string | null; + owner: string | null; + repository: string | null; + issue: string | null; +} + +export type Parser = ( + message: string, + options: ParserOptions +) => Omit; + +export interface ParserOptions { + commentChar?: string; + headerCorrespondence?: string[]; + headerPattern?: RegExp; + issuePrefixes?: string[]; + mergeCorrespondence?: string[]; + mergePattern?: RegExp; + noteKeywords?: string[]; + revertCorrespondence?: string[]; + revertPattern?: RegExp; +} diff --git a/@commitlint/parse/tsconfig.json b/@commitlint/parse/tsconfig.json new file mode 100644 index 0000000000..f4a57643f0 --- /dev/null +++ b/@commitlint/parse/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.shared.json", + "compilerOptions": { + "composite": true, + "rootDir": "./src", + "outDir": "./lib" + }, + "include": [ + "./src" + ], + "exclude": [ + "./src/**/*.test.ts", + "./lib/**/*" + ] +} diff --git a/tsconfig.json b/tsconfig.json index 48225a91cc..e710a7f3bd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,8 +7,9 @@ { "path": "@commitlint/execute-rule" }, { "path": "@commitlint/format" }, { "path": "@commitlint/is-ignored" }, + { "path": "@commitlint/parse" }, { "path": "@commitlint/resolve-extends" }, { "path": "@commitlint/to-lines" }, { "path": "@commitlint/top-level" }, ] -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 922c12fd4e..f829f9b116 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1285,7 +1285,7 @@ dependencies: "@types/jest-diff" "*" -"@types/lodash@4.14.136": +"@types/lodash@4.14.136", "@types/lodash@^4.14.136": version "4.14.136" resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.136.tgz#413e85089046b865d960c9ff1d400e04c31ab60f" integrity sha512-0GJhzBdvsW2RUccNHOBkabI8HZVdOXmXbXhuKlDEd5Vv12P7oAVGfomGp3Ne21o5D/qu1WmthlNKFaoZJJeErA== @@ -10913,7 +10913,7 @@ typedarray@^0.0.6: resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@3.5.3: +typescript@3.5.3, typescript@^3.5.3: version "3.5.3" resolved "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==