Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(prompt): migrate prompt to typescript #2371

Merged
merged 8 commits into from Jan 4, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 2 additions & 19 deletions @commitlint/prompt/package.json
Expand Up @@ -7,20 +7,8 @@
"lib/"
],
"scripts": {
"build": "cross-env NODE_ENV=production babel src --out-dir lib --source-maps",
"commit": "git-cz",
"deps": "dep-check",
"pkg": "pkg-check --skip-import",
"start": "yarn run watch",
"watch": "babel src --out-dir lib --watch --source-maps"
},
"babel": {
"presets": [
"commitlint"
],
"ignore": [
"**/*.test.js"
]
"pkg": "pkg-check --skip-import"
},
"config": {
"commitizen": {
Expand Down Expand Up @@ -48,15 +36,10 @@
"node": ">=v10.22.1"
},
"devDependencies": {
"@babel/cli": "^7.11.6",
"@babel/core": "^7.11.6",
"@commitlint/utils": "^11.0.0",
"babel-preset-commitlint": "^11.0.0",
"commitizen": "4.2.2",
"cross-env": "7.0.3"
"commitizen": "4.2.2"
},
"dependencies": {
"@babel/runtime": "^7.11.2",
"@commitlint/load": "^11.0.0",
"chalk": "^4.0.0",
"lodash": "^4.17.19",
Expand Down
13 changes: 0 additions & 13 deletions @commitlint/prompt/src/index.js

This file was deleted.

17 changes: 17 additions & 0 deletions @commitlint/prompt/src/index.ts
@@ -0,0 +1,17 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import vorpal from 'vorpal';
import input from './input';

type Commit = (input: string) => void;

/**
* Entry point for commitizen
* @param _ inquirer instance passed by commitizen, unused
* @param commit callback to execute with complete commit message
* @return generated commit message
*/
export const prompter = async (_: unknown, commit: Commit): Promise<void> => {
const message = await input(vorpal);
commit(message);
};
51 changes: 25 additions & 26 deletions @commitlint/prompt/src/input.js → @commitlint/prompt/src/input.ts
Expand Up @@ -5,17 +5,19 @@ import format from './library/format';
import getHasName from './library/get-has-name';
import getPrompt from './library/get-prompt';
import settings from './settings';
import {InputSetting, Prompter, Result, RuleEntry} from './library/types';
import {QualifiedRules} from '@commitlint/types';

export default input;

/**
* Get user input by interactive prompt based on
* conventional-changelog-lint rules.
* @param {function} prompter
* @return {Promise<string>} commit message
* @param prompter
* @return commit message
*/
async function input(prompter) {
const results = {
async function input(prompter: () => Prompter): Promise<string> {
const results: Result = {
type: null,
scope: null,
subject: null,
Expand All @@ -26,20 +28,24 @@ async function input(prompter) {
const {rules} = await load();

await Promise.all(
['type', 'scope', 'subject', 'body', 'footer'].map(
(['type', 'scope', 'subject', 'body', 'footer'] as const).map(
throat(1, async (input) => {
const inputRules = getRules(input, rules);
const inputSettings = settings[input];
const inputSettings: InputSetting = settings[input];

const isHeader = ['type', 'scope', 'subject'].indexOf(input) > -1;

const headerLengthRule = getRules('header', rules).filter(
const headerLengthRule = getRules('header', rules).find(
getHasName('max-length')
)[0];
);

if (isHeader && headerLengthRule) {
const [, [severity, applicable, length]] = headerLengthRule;
if (severity > 0 && applicable === 'always') {
if (
severity > 0 &&
applicable === 'always' &&
typeof length === 'number'
) {
inputSettings.header = {
length,
};
Expand All @@ -65,30 +71,23 @@ async function input(prompter) {

/**
* Get prefix for a given rule id
* @param {string} id of the rule
* @return {string} prefix of the rule
* @param id of the rule
* @return prefix of the rule
*/
function getRulePrefix(id) {
function getRulePrefix(id: string) {
const fragments = id.split('-');
const [prefix] = fragments;
return fragments.length > 1 ? prefix : null;
}

/**
* Get a predecate matching rule definitions with a given prefix
* @param {[type]} name [description]
* @return {[type]} [description]
*/
function getHasPrefix(name) {
return (rule) => getRulePrefix(rule[0]) === name;
}

/**
* Get rules for a given prefix
* @param {string} prefix to search in rule names
* @param {object} rules rules to search in
* @return {object} rules matching the prefix search
* @param prefix to search in rule names
* @param rules rules to search in
* @return rules matching the prefix search
*/
function getRules(prefix, rules) {
return Object.entries(rules).filter(getHasPrefix(prefix));
function getRules(prefix: string, rules: QualifiedRules) {
return Object.entries(rules).filter(
(rule): rule is RuleEntry => getRulePrefix(rule[0]) === prefix
);
}
12 changes: 0 additions & 12 deletions @commitlint/prompt/src/library/enum-rule-is-active.js

This file was deleted.

20 changes: 20 additions & 0 deletions @commitlint/prompt/src/library/enum-rule-is-active.ts
@@ -0,0 +1,20 @@
import ruleIsApplicable from './rule-is-applicable';
import ruleIsActive from './rule-is-active';
import {RuleEntry} from './types';
import {RuleConfigSeverity} from '@commitlint/types';

export default function enumRuleIsActive(
rule: RuleEntry
): rule is [
string,
Readonly<
[RuleConfigSeverity.Warning | RuleConfigSeverity.Error, 'always', string[]]
>
] {
return (
ruleIsActive(rule) &&
ruleIsApplicable(rule) &&
Array.isArray(rule[1][2]) &&
rule[1][2].length > 0
);
}
@@ -1,18 +1,18 @@
import chalk from 'chalk';
import {Result} from './types';

export default format;

/**
* Get formatted commit message
* @param {object} input object containing structured results
* @param {boolean} debug show debug information in commit message
* @return {string} formatted debug message
* @param input object containing structured results
* @param debug show debug information in commit message
* @return formatted debug message
*/
function format(input, debug = false) {
function format(input: Result, debug = false): string {
const results = debug
? Object.entries(input || {}).reduce((registry, item) => {
const [name, value] = item;
registry[name] =
? Object.entries(input).reduce<Result>((registry, [name, value]) => {
registry[name as 'type' | 'scope' | 'subject' | 'body' | 'footer'] =
value === null ? chalk.grey(`<${name}>`) : chalk.bold(value);
return registry;
}, {})
Expand Down
Expand Up @@ -3,14 +3,17 @@ import kebabCase from 'lodash/kebabCase';
import snakeCase from 'lodash/snakeCase';
import upperFirst from 'lodash/upperFirst';
import startCase from 'lodash/startCase';
import {RuleEntry} from './types';

/**
* Get forced case for rule
* @param {object} rule to parse
* @return {fn} transform function applying the enforced case
* @param rule to parse
* @return transform function applying the enforced case
*/
export default function getForcedCaseFn(rule) {
const noop = (input) => input;
export default function getForcedCaseFn(
rule?: RuleEntry
): (input: string) => string {
const noop = (input: string) => input;

if (!rule) {
return noop;
Expand All @@ -25,13 +28,13 @@ export default function getForcedCaseFn(rule) {
const [level] = config;

if (level === 0) {
return;
return noop;
}

const [, when] = config;

if (when === 'never') {
return;
return noop;
}

const [, , target] = config;
Expand All @@ -42,27 +45,27 @@ export default function getForcedCaseFn(rule) {

switch (target) {
case 'camel-case':
return (input) => camelCase(input);
return (input: string) => camelCase(input);
case 'kebab-case':
return (input) => kebabCase(input);
return (input: string) => kebabCase(input);
case 'snake-case':
return (input) => snakeCase(input);
return (input: string) => snakeCase(input);
case 'pascal-case':
return (input) => upperFirst(camelCase(input));
return (input: string) => upperFirst(camelCase(input));
case 'start-case':
return (input) => startCase(input);
return (input: string) => startCase(input);
case 'upper-case':
case 'uppercase':
return (input) => input.toUpperCase();
return (input: string) => input.toUpperCase();
case 'sentence-case':
case 'sentencecase':
return (input) =>
return (input: string) =>
`${input.charAt(0).toUpperCase()}${input.substring(1).toLowerCase()}`;
case 'lower-case':
case 'lowercase':
case 'lowerCase': // Backwards compat config-angular v4
return (input) => input.toLowerCase() === input;
return (input: string) => input.toLowerCase();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in case of lowecase this code was producing invalid result

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

Copy link
Contributor Author

@armano2 armano2 Dec 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i found out why this code did not crash for anyone, few lines above we have:

	const [config] = rule;
	if (!Array.isArray(config)) {
		return noop;
	}

and this condition never passes as we should use

	const [, config] = rule;

default:
throw new TypeError(`Unknown target case "${rule[2]}"`);
throw new TypeError(`Unknown target case "${target}"`);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rule[2] is never defined, it has to be config[2] or target to print correct value

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch again, I think you are right with the target here. Looks like target is also config[2] 😄

}
}
@@ -1,15 +1,19 @@
import {RuleEntry} from './types';

/**
* Get forced leading for rule
* @param {object} rule to parse
* @return {fn} transform function applying the leading
* @param rule to parse
* @return transform function applying the leading
*/
export default function getForcedLeadingFn(rule) {
const noop = (input) => input;
const remove = (input) => {
export default function getForcedLeadingFn(
rule?: RuleEntry
): (input: string) => string {
const noop = (input: string): string => input;
const remove = (input: string): string => {
const fragments = input.split('\n');
return fragments[0] === '' ? fragments.slice(1).join('\n') : input;
};
const lead = (input) => {
const lead = (input: string): string => {
const fragments = input.split('\n');
return fragments[0] === '' ? input : ['', ...fragments].join('\n');
};
Expand All @@ -32,7 +36,7 @@ export default function getForcedLeadingFn(rule) {
* @param {object} rule to parse
* @return {boolean|null} transform function applying the leading
*/
function getForcedLeading(rule) {
function getForcedLeading(rule: RuleEntry) {
if (!rule) {
return null;
}
Expand Down
10 changes: 0 additions & 10 deletions @commitlint/prompt/src/library/get-has-name.js

This file was deleted.

11 changes: 11 additions & 0 deletions @commitlint/prompt/src/library/get-has-name.ts
@@ -0,0 +1,11 @@
import getRuleName from './get-rule-name';
import {RuleEntry} from './types';

/**
* Get a predicate matching rule definitions with a given name
*/
export default function getHasName(name: string) {
return <T extends RuleEntry>(
rule: RuleEntry
): rule is Exclude<T, [string, undefined]> => getRuleName(rule[0]) === name;
}