Skip to content
This repository has been archived by the owner on Oct 18, 2023. It is now read-only.

feat: name casing #2

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions actions/add.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
shouldAskForProject,
} from '../lib/utils/project-utils';
import { AbstractAction } from './abstract.action';
import { CaseType } from '../lib/utils/formatting';

const schematicName = 'nest-add';

Expand Down Expand Up @@ -122,10 +123,13 @@ export class AddAction extends AbstractAction {
) {
console.info(MESSAGES.LIBRARY_INSTALLATION_STARTS);
const schematicOptions: SchematicOption[] = [];
const caseType = options
.find((option) => option.name === 'case')?.value as CaseType
schematicOptions.push(
new SchematicOption(
'sourceRoot',
options.find((option) => option.name === 'sourceRoot')!.value as string,
caseType
),
);
const extraFlagsString = extraFlags ? extraFlags.join(' ') : undefined;
Expand Down
19 changes: 11 additions & 8 deletions actions/generate.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
shouldGenerateSpec,
} from '../lib/utils/project-utils';
import { AbstractAction } from './abstract.action';
import { CaseType } from '../lib/utils/formatting';

export class GenerateAction extends AbstractAction {
public async handle(inputs: Input[], options: Input[]) {
Expand All @@ -41,12 +42,14 @@ const generateFiles = async (inputs: Input[]) => {
(option) => option.name === 'specFileSuffix',
);

const caseType = inputs.find((option) => option.name === 'case')!.value as CaseType;

const collection: AbstractCollection = CollectionFactory.create(
collectionOption || configuration.collection || Collection.NESTJS,
);
const schematicOptions: SchematicOption[] = mapSchematicOptions(inputs);
const schematicOptions: SchematicOption[] = mapSchematicOptions(inputs, caseType);
schematicOptions.push(
new SchematicOption('language', configuration.language),
new SchematicOption('language', configuration.language, caseType),
);
const configurationProjects = configuration.projects;

Expand Down Expand Up @@ -125,11 +128,11 @@ const generateFiles = async (inputs: Input[]) => {
}
}

schematicOptions.push(new SchematicOption('sourceRoot', sourceRoot));
schematicOptions.push(new SchematicOption('spec', generateSpec));
schematicOptions.push(new SchematicOption('flat', generateFlat));
schematicOptions.push(new SchematicOption('sourceRoot', sourceRoot, caseType));
schematicOptions.push(new SchematicOption('spec', generateSpec, caseType));
schematicOptions.push(new SchematicOption('flat', generateFlat, caseType));
schematicOptions.push(
new SchematicOption('specFileSuffix', generateSpecFileSuffix),
new SchematicOption('specFileSuffix', generateSpecFileSuffix, caseType),
);
try {
const schematicInput = inputs.find((input) => input.name === 'schematic');
Expand All @@ -144,12 +147,12 @@ const generateFiles = async (inputs: Input[]) => {
}
};

const mapSchematicOptions = (inputs: Input[]): SchematicOption[] => {
const mapSchematicOptions = (inputs: Input[], caseType: CaseType): SchematicOption[] => {
const excludedInputNames = ['schematic', 'spec', 'flat', 'specFileSuffix'];
const options: SchematicOption[] = [];
inputs.forEach((input) => {
if (!excludedInputNames.includes(input.name) && input.value !== undefined) {
options.push(new SchematicOption(input.name, input.value));
options.push(new SchematicOption(input.name, input.value, caseType));
Copy link

Choose a reason for hiding this comment

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

I would introduce a key-value options here since it has 3 arguments.

Copy link
Author

@espoal espoal Jul 27, 2023

Choose a reason for hiding this comment

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

You mean adding something like SchmaticOption('naming', 'camelCase')?
That's the first thing I tried but I couldn't make it work because when we launch the command nest generate controller emails --naming=camelCase we have two schematic options:

  • SchmaticOption('name', 'emails')
  • SchmaticOption('suffix', 'controller')

We need to apply the camelCase transform to both, hence why I used a 3 way option.
If I misunderstood you please let me know, or maybe you can see something I'm not seeing.
One option could be to open a draft PR and ask the nestjs authors for advice.

Copy link
Author

@espoal espoal Jul 28, 2023

Choose a reason for hiding this comment

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

Proposal: nameCase becomes an extensible object (additionalParameters = { caseNaming: hello })

}
});
return options;
Expand Down
2 changes: 1 addition & 1 deletion actions/new.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ const mapSchematicOptions = (options: Input[]): SchematicOption[] => {
return options.reduce(
(schematicOptions: SchematicOption[], option: Input) => {
if (option.name !== 'skip-install') {
schematicOptions.push(new SchematicOption(option.name, option.value));
schematicOptions.push(new SchematicOption(option.name, option.value, 'kebab'));
}
return schematicOptions;
},
Expand Down
9 changes: 9 additions & 0 deletions commands/generate.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ export class GenerateCommand extends AbstractCommand {
'-c, --collection [collectionName]',
'Schematics collection to use.',
)
.option(
'--case [case]',
Copy link

Choose a reason for hiding this comment

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

--naming

Copy link
Author

Choose a reason for hiding this comment

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

--caseNaming
--fileCaseNaming
--directoryCaseNaming
--variableCaseNaming

'Case for variable naming. Options are \'kebab\' | \'snake\' | \'camel\' | \'pascal\' | \'upper\'.',
)
.action(
async (
schematic: string,
Expand Down Expand Up @@ -93,6 +97,11 @@ export class GenerateCommand extends AbstractCommand {
value: command.skipImport,
});

options.push({
name: 'case',
value: command.case,
});

const inputs: Input[] = [];
inputs.push({ name: 'schematic', value: schematic });
inputs.push({ name: 'name', value: name });
Expand Down
23 changes: 12 additions & 11 deletions lib/schematics/schematic.option.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { normalizeToKebabOrSnakeCase } from '../utils/formatting';
import { normalizeToCase, formatString, CaseType } from '../utils/formatting';

export class SchematicOption {
constructor(private name: string, private value: boolean | string) {}
constructor(private name: string, private value: boolean | string, private caseType: CaseType) {}
Copy link
Author

Choose a reason for hiding this comment

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

I decided to pass CaseType to the constructor of SchematicOption, so that I can pass the flag to the schematics package and I can format each field accordingly.
This approach adds more noise than I would like, so I'm open to a better solution if anyone can find it


get normalizedName() {
return normalizeToKebabOrSnakeCase(this.name);
return normalizeToCase(this.name, 'kebab');
}

public toCommandString(): string {
Expand All @@ -25,13 +25,14 @@ export class SchematicOption {
}

private format() {
return normalizeToKebabOrSnakeCase(this.value as string)
.split('')
.reduce((content, char) => {
if (char === '(' || char === ')' || char === '[' || char === ']') {
return `${content}\\${char}`;
}
return `${content}${char}`;
}, '');

return formatString(
normalizeToCase(
this.value as string,
this.caseType
)
);
}
}


47 changes: 47 additions & 0 deletions lib/utils/formatting.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,48 @@
import { CaseToCase } from 'case-to-case'
Copy link
Author

Choose a reason for hiding this comment

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

I'm not sure this is the best package to do this


const formatter = new CaseToCase()

export type CaseType = 'kebab' | 'snake' | 'camel' | 'pascal' | 'upper';

/**
*
* @param str
* @param caseType
* @returns formated string
* @description normalizes input to supported path and file name format.
* Changes camelCase strings to kebab-case, replaces spaces with dash and keeps underscores.
*/
export const normalizeToCase = (str: string, caseType: CaseType = 'kebab') => {
switch (caseType) {
case 'kebab':
return normalizeToKebabOrSnakeCase(str);
case 'snake':
return normalizeToKebabOrSnakeCase(str);
case 'camel':
return formatter.toCamelCase(str);
case 'pascal':
return formatter.toPascalCase(str);
case 'upper':
return formatter.toUpperCase(str);
default:
console.log(`Error! case type ${caseType} is not supported.`)
return str;

}
}

export const formatString = (str: string) => {
return str.split('')
.reduce((content, char) => {
if (char === '(' || char === ')' || char === '[' || char === ']') {
return `${content}\\${char}`;
}
return `${content}${char}`;
}, '')
}



/**
*
* @param str
Expand All @@ -13,3 +58,5 @@ export function normalizeToKebabOrSnakeCase(str: string) {
.toLowerCase()
.replace(STRING_DASHERIZE_REGEXP, '-');
}


15 changes: 13 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nestjs/cli",
"version": "10.1.8",
"version": "10.2.0",
"description": "Nest - modern, fast, powerful node.js web framework (@cli)",
"publishConfig": {
"access": "public"
Expand Down Expand Up @@ -42,6 +42,7 @@
"@angular-devkit/schematics": "16.1.3",
"@angular-devkit/schematics-cli": "16.1.3",
"@nestjs/schematics": "^10.0.1",
"case-to-case": "1.2.0",
"chalk": "4.1.2",
"chokidar": "3.5.3",
"cli-table3": "0.6.3",
Expand Down
4 changes: 2 additions & 2 deletions test/lib/schematics/schematic.option.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ describe('Schematic Option', () => {

tests.forEach((test) => {
it(test.description, () => {
const option = new SchematicOption(test.option, test.input);
const option = new SchematicOption(test.option, test.input, 'kebab');

if (isFlagTest(test)) {
if (test.input) {
Expand All @@ -117,7 +117,7 @@ describe('Schematic Option', () => {
});

it('should manage boolean option', () => {
const option = new SchematicOption('dry-run', false);
const option = new SchematicOption('dry-run', false, 'kebab');
expect(option.toCommandString()).toEqual('--no-dry-run');
});
});
33 changes: 33 additions & 0 deletions test/lib/utils/formatting.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { normalizeToCase, formatString, CaseType } from '../../../lib/utils/formatting';
import {SchematicOption} from "../../../lib/schematics";

type TestSuite = {
description: string;
input: string;
caseType: CaseType;
expected: string;
};

describe('Format strings', () => {

const tests: TestSuite[] = [
{
description: 'From kebap to camel',
input: 'my-app',
caseType: 'camel',
expected: 'myApp',
},
];

tests.forEach((test) => {
it(test.description, () => {

expect(
normalizeToCase(test.input, test.caseType)
).toEqual(
test.expected
);
});
});

});