199 lines
5.2 KiB
TypeScript

// TODO(jack): Remove inline renderHook function when RTL releases with its own version
import {
applyChangesToString,
formatFiles,
generateFiles,
getProjects,
joinPathFragments,
logger,
names,
toJS,
Tree,
} from '@nx/devkit';
import { Schema } from './schema';
import { addImport } from '../../utils/ast-utils';
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
import { join } from 'path';
import { determineArtifactNameAndDirectoryOptions } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
interface NormalizedSchema extends Schema {
projectSourceRoot: string;
hookName: string;
hookTypeName: string;
fileName: string;
projectName: string;
}
export async function hookGenerator(host: Tree, schema: Schema) {
return hookGeneratorInternal(host, {
nameAndDirectoryFormat: 'derived',
...schema,
});
}
export async function hookGeneratorInternal(host: Tree, schema: Schema) {
const options = await normalizeOptions(host, schema);
createFiles(host, options);
addExportsToBarrel(host, options);
return await formatFiles(host);
}
function createFiles(host: Tree, options: NormalizedSchema) {
generateFiles(host, join(__dirname, './files'), options.directory, {
...options,
tmpl: '',
});
for (const c of host.listChanges()) {
let deleteFile = false;
if (options.skipTests && /.*spec.ts/.test(c.path)) {
deleteFile = true;
}
if (deleteFile) {
host.delete(c.path);
}
}
if (options.js) {
toJS(host);
}
}
let tsModule: typeof import('typescript');
function addExportsToBarrel(host: Tree, options: NormalizedSchema) {
if (!tsModule) {
tsModule = ensureTypescript();
}
const workspace = getProjects(host);
const isApp =
workspace.get(options.projectName).projectType === 'application';
if (options.export && !isApp) {
const indexFilePath = joinPathFragments(
options.projectSourceRoot,
options.js ? 'index.js' : 'index.ts'
);
const indexSource = host.read(indexFilePath, 'utf-8');
if (indexSource !== null) {
const indexSourceFile = tsModule.createSourceFile(
indexFilePath,
indexSource,
tsModule.ScriptTarget.Latest,
true
);
const changes = applyChangesToString(
indexSource,
addImport(
indexSourceFile,
`export * from './${options.directory}/${options.fileName}';`
)
);
host.write(indexFilePath, changes);
}
}
}
async function normalizeOptions(
host: Tree,
options: Schema
): Promise<NormalizedSchema> {
assertValidOptions(options);
const {
artifactName: name,
directory: _directory,
fileName: _fileName,
nameAndDirectoryFormat,
project: projectName,
} = await determineArtifactNameAndDirectoryOptions(host, {
artifactType: 'hook',
callingGenerator: '@nx/react:hook',
name: options.name,
directory: options.directory,
derivedDirectory: options.directory,
flat: options.flat,
nameAndDirectoryFormat: options.nameAndDirectoryFormat,
project: options.project,
fileExtension: 'tsx',
pascalCaseFile: options.pascalCaseFiles,
pascalCaseDirectory: options.pascalCaseDirectory,
});
let base = _fileName;
if (base.startsWith('use-')) {
base = base.substring(4);
} else if (base.startsWith('use')) {
base = base.substring(3);
}
const { className, fileName } = names(base);
// If using `as-provided` file and directory, then don't normalize.
// Otherwise, support legacy behavior of prefixing filename with `use-`.
const hookFilename =
nameAndDirectoryFormat === 'as-provided'
? fileName
: options.pascalCaseFiles
? 'use'.concat(className)
: 'use-'.concat(fileName);
const hookName = 'use'.concat(className);
const hookTypeName = 'Use'.concat(className);
const project = getProjects(host).get(projectName);
const { sourceRoot: projectSourceRoot, projectType } = project;
if (options.export && projectType === 'application') {
logger.warn(
`The "--export" option should not be used with applications and will do nothing.`
);
}
// Support legacy behavior of derived directory to prefix with `use-`.
let directory = _directory;
if (nameAndDirectoryFormat === 'derived') {
const parts = directory.split('/');
parts.pop();
if (!options.flat) {
parts.push(
options.pascalCaseDirectory
? 'use'.concat(className)
: 'use-'.concat(fileName)
);
}
directory = parts.join('/');
}
return {
...options,
directory,
hookName,
hookTypeName,
fileName: hookFilename,
projectSourceRoot,
projectName,
};
}
function assertValidOptions(options: Schema) {
const slashes = ['/', '\\'];
slashes.forEach((s) => {
if (options.name.indexOf(s) !== -1) {
const [name, ...rest] = options.name.split(s).reverse();
let suggestion = rest.map((x) => x.toLowerCase()).join(s);
if (options.directory) {
suggestion = `${options.directory}${s}${suggestion}`;
}
throw new Error(
`Found "${s}" in the hook name. Did you mean to use the --directory option (e.g. \`nx g c ${name} --directory ${suggestion}\`)?`
);
}
});
}
export default hookGenerator;