@knighted/display-name
Advanced tools
Comparing version
@@ -1,3 +0,8 @@ | ||
declare const transform: (source: string) => Promise<string>; | ||
declare const transformFile: (filename: string) => Promise<string>; | ||
export { transform, transformFile }; | ||
type Options = { | ||
requirePascal?: boolean; | ||
insertSemicolon?: boolean; | ||
}; | ||
declare const modify: (source: string, options?: Options) => Promise<string>; | ||
declare const modifyFile: (filename: string, options?: Options) => Promise<string>; | ||
export { modify, modifyFile }; | ||
export type { Options }; |
import { readFile } from 'node:fs/promises'; | ||
import { parseSync } from 'oxc-parser'; | ||
import MagicString from 'magic-string'; | ||
import { asyncAncestorWalk, ancestorWalk, walk } from '@knighted/walk'; | ||
import { parseSync } from 'oxc-parser'; | ||
const pascal = /^[A-Z][a-zA-Z0-9]*$/; | ||
const collectDisplayNames = async (node) => { | ||
@@ -29,6 +30,25 @@ const foundDisplayNames = []; | ||
}; | ||
const transform = async (source) => { | ||
const hasJsx = async (body) => { | ||
let found = false; | ||
await walk(body, { | ||
enter(node) { | ||
if (!found && (node.type === 'JSXElement' || node.type === 'JSXFragment')) { | ||
found = true; | ||
} | ||
}, | ||
}); | ||
return found; | ||
}; | ||
const defaultOptions = { | ||
requirePascal: true, | ||
insertSemicolon: true, | ||
}; | ||
const modify = async (source, options = defaultOptions) => { | ||
const ast = parseSync('file.tsx', source); | ||
const code = new MagicString(source); | ||
const foundDisplayNames = await collectDisplayNames(ast.program); | ||
const opts = { | ||
...defaultOptions, | ||
...options, | ||
}; | ||
await asyncAncestorWalk(ast.program, { | ||
@@ -41,27 +61,21 @@ async enter(node, ancestors) { | ||
const { body, id } = node; | ||
if (body) { | ||
await walk(body, { | ||
enter(bodyNode) { | ||
switch (bodyNode.type) { | ||
case 'JSXElement': | ||
case 'JSXFragment': | ||
{ | ||
if (!id) { | ||
/** | ||
* If the function expression is not named, | ||
* use the varible nameto set the displayName. | ||
*/ | ||
const declarator = ancestors.findLast(ancestor => ancestor.type === 'VariableDeclarator'); | ||
if (declarator && | ||
declarator.id.type === 'Identifier' && | ||
!foundDisplayNames.includes(declarator.id.name)) { | ||
const name = declarator.id.name; | ||
code.appendRight(declarator.end, `\n${name}.displayName = '${name}';`); | ||
} | ||
} | ||
} | ||
break; | ||
if (!id && body && (await hasJsx(body))) { | ||
/** | ||
* If the function expression is not named, | ||
* use the varible name to set the displayName. | ||
*/ | ||
const declaratorIndex = ancestors.findLastIndex(ancestor => ancestor.type === 'VariableDeclarator'); | ||
if (declaratorIndex !== -1) { | ||
const declarator = ancestors[declaratorIndex]; | ||
if (declarator.type === 'VariableDeclarator' && | ||
declarator.id.type === 'Identifier' && | ||
!foundDisplayNames.includes(declarator.id.name)) { | ||
const { name } = declarator.id; | ||
const declaration = ancestors[declaratorIndex - 1]; | ||
if (declaration.type === 'VariableDeclaration' && | ||
(!opts.requirePascal || pascal.test(name))) { | ||
code.appendRight(declarator.end + 1, `\n${name}.displayName = '${name}'${opts.insertSemicolon ? ';' : ''}`); | ||
} | ||
}, | ||
}); | ||
} | ||
} | ||
} | ||
@@ -75,5 +89,5 @@ } | ||
}; | ||
const transformFile = async (filename) => { | ||
return transform((await readFile(filename, 'utf-8')).toString()); | ||
const modifyFile = async (filename, options = defaultOptions) => { | ||
return modify((await readFile(filename, 'utf-8')).toString(), options); | ||
}; | ||
export { transform, transformFile }; | ||
export { modify, modifyFile }; |
{ | ||
"name": "@knighted/display-name", | ||
"version": "1.0.0-alpha.0", | ||
"version": "1.0.0-alpha.1", | ||
"description": "Codemod to add React displayName properties to function components.", | ||
@@ -5,0 +5,0 @@ "type": "module", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
13114
5.45%195
20.37%