tunnckoCore/gibon

View on GitHub
@packages/parse-commit-message/src/header.ts

Summary

Maintainability
A
0 mins
Test Coverage
import type { Header, Options, Result, SimpleHeader } from './types.ts';
import { isValidString, stringToHeader } from './utils.ts';

/**
 * Parses given `header` string into an header object.
 * Basically the same as [.parse](#parse), except that
 * it only can accept single string and returns a `Header` object.
 *
 * _The `parse*` methods are not doing any checking and validation,
 * so you may want to pass the result to `validateHeader` or `checkHeader`,
 * or to `validateHeader` with `ret` option set to `true`._
 *
 * @example
 * import { parseHeader } from 'parse-commit-message';
 *
 * const longCommitMsg = `fix: bar qux
 *
 * Awesome body!`;
 *
 * const headerObj = parseCommit(longCommitMsg);
 * console.log(headerObj);
 * // => { type: 'fix', scope: null, subject: 'bar qux' }
 *
 * @name  .parseHeader
 * @param {string} header a header stirng like `'fix(foo): bar baz'`
 * @param {object} options options to control the header regex and case sensitivity
 * @param {RegExp|string} options.headerRegex string regular expression or instance of RegExp
 * @param {boolean} options.caseSensitive whether or not to be case sensitive, defaults to `false`
 * @returns {Header} a `Header` object like `{ type, scope?, subject }`
 * @public
 */
export function parseHeader(header: string, options?: Options): Header {
  if (!isValidString(header)) {
    throw new TypeError('expect `header` to be non empty string');
  }

  return stringToHeader(header.trim(), options);
}

/**
 * Receives a `header` object, validates it using `validateHeader`,
 * builds a "header" string and returns it. Method throws if problems found.
 * Basically the same as [.stringify](#stringify), except that
 * it only can accept single `Header` object.
 *
 * @example
 * import { stringifyHeader } from 'parse-commit-message';
 *
 * const headerStr = stringifyCommit({ type: 'foo', subject: 'bar qux' });
 * console.log(headerStr); // => 'foo: bar qux'
 *
 * @name  .stringifyHeader
 * @param {Header|SimpleHeader} header a `Header` object like `{ type, scope?, subject }` or `{ value: string }`
 * @param {object} options options to control the header regex and case sensitivity
 * @param {RegExp|string} options.headerRegex string regular expression or instance of RegExp
 * @param {boolean} options.caseSensitive whether or not to be case sensitive, defaults to `false`
 * @returns {string} a header stirng like `'fix(foo): bar baz'`
 * @public
 */
export function stringifyHeader(
  header: Header | SimpleHeader,
  options?: Options,
): `${string}: ${string}` {
  const res = validateHeader(header, options) as Result<Header>;

  if (res.error) {
    throw res.error;
  }

  // if SimpleHeader (res.value is like { value: 'chore: foobar' })
  // TODO(@tunnckoCore): not sure...
  /* istanbul ignore next */
  if (res.value && typeof res.value === 'object' && 'value' in res.value) {
    return res.value.value as `${string}: ${string}`;
  }

  const { type, scope, subject } = res.value as Header;

  return `${type}${scope ? `(${scope})` : ''}: ${subject}`.trim() as `${string}: ${string}`;
}

/**
 * Validates given `header` object and returns `boolean`.
 * You may want to pass `ret` to return an object instead of throwing.
 * Basically the same as [.validate](#validate), except that
 * it only can accept single `Header` object.
 *
 * @example
 * import { validateHeader } from 'parse-commit-message';
 *
 * const header = { type: 'foo', subject: 'bar qux' };
 *
 * const headerIsValid = validateHeader(header);
 * console.log(headerIsValid); // => true
 *
 * const { value } = validateHeader(header, true);
 * console.log(value);
 * // => {
 * //   header: { type: 'foo', scope: null, subject: 'bar qux' },
 * //   body: 'okey dude',
 * //   footer: null,
 * // }
 *
 * const { error } = validateHeader({
 *   type: 'bar'
 * }, true);
 *
 * console.log(error);
 * // => TypeError: header.subject should be non empty string
 *
 * @name  .validateHeader
 * @param {Header|SimpleHeader} header a `Header` object like `{ type, scope?, subject }` or `{ value: string }`
 * @param {object} options options to control the header regex and case sensitivity
 * @param {RegExp|string} options.headerRegex string regular expression or instance of RegExp
 * @param {boolean} options.caseSensitive whether or not to be case sensitive, defaults to `false`
 * @returns {Result} an object like `{ value: Array<Commit>, error: Error }`
 * @public
 */
export function validateHeader(header: Header | SimpleHeader, options?: Options) {
  const result = {} as Result<Header>;

  try {
    result.value = checkHeader(header, options) as Header;
  } catch (err) {
    return { error: err } as Result<any>;
  }

  return result as Result<Header>;
}

/**
 * Receives a `Header` and checks if it is valid.
 * Basically the same as [.check](#check), except that
 * it only can accept single `Header` object.
 *
 * @example
 * import { checkHeader } from 'parse-commit-message';
 *
 * try {
 *   checkHeader({ type: 'fix' });
 * } catch(err) {
 *   console.log(err);
 *   // => TypeError: header.subject should be non empty string
 * }
 *
 * // throws because can accept only Header objects
 * checkHeader('foo bar baz');
 * checkHeader(123);
 * checkHeader([]);
 * checkHeader([{ type: 'foo', subject: 'bar' }]);
 *
 * @name  .checkHeader
 * @param {Header|SimpleHeader} header a `Header` object like `{ type, scope?, subject }` or `{ value: string }`
 * @param {object} options options to control the header regex and case sensitivity
 * @param {RegExp|string} options.headerRegex string regular expression or instance of RegExp
 * @param {boolean} options.caseSensitive whether or not to be case sensitive, defaults to `false`
 * @returns {Header} returns the same as given if no problems, otherwise it will throw.
 * @public
 */
export function checkHeader(header: Header | SimpleHeader, options?: Options): Header {
  if (header && typeof header === 'object' && 'value' in header) {
    const { value } = header;
    return stringToHeader(value, options);
  }
  // else: we have Header

  if (!isValidString(header?.type)) {
    throw new TypeError('header.type should be non empty string');
  }
  if (!isValidString(header?.subject)) {
    throw new TypeError('header.subject should be non empty string');
  }

  const isValidScope =
    header && 'scope' in header && header.scope !== null ? isValidString(header.scope) : true;

  if (!isValidScope) {
    throw new TypeError('commit.header.scope should be non empty string when given');
  }

  return { scope: null, ...header };
}