diff --git a/.gitignore b/.gitignore index b512c09..944c283 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -node_modules \ No newline at end of file +node_modules +.idea/ diff --git a/playground.cjs b/playground.cjs index a8bdabd..0ee67ec 100644 --- a/playground.cjs +++ b/playground.cjs @@ -1,22 +1,54 @@ const { transformAsync } = require("@babel/core") +// Change the following code and and run `pnpm run playground` +const javascriptSrc =/*javascript*/` +import { Component } from 'solid-js' +import { component } from 'undestructure-macros' + +component(({ a, b, c, ...other }) =>
{a} {b} {c} {other.d}
) + +const ArrowComp = ({ a, b, c, ...other }) =>
{a} {b} {c} {other.d}
+ +const FunctionExpressionComp = function ({ a, b, c, ...other }) { return
{a} {b} {c} {other.d}
; } + +const NamedExpressionComp = function Comp({ a, b, c, ...other }) { return
{a} {b} {c} {other.d}
; } + +function FunctionComp({ a, b, c, ...other }) { return
{a} {b} {c} {other.d}
; } + +function notAComponent({ a, b, c, ...other }) { return 2 + 2; } +`; // Change the following code and and run `pnpm run playground` -const src=/*javascript*/` +const typeScriptSrc =/*typescript*/` import { Component } from 'solid-js' -import x from 'undestructure-macros' -const MyComp: Component = ({ a, b, c, ...other }) => {a; b; c; other;} -` - - -;(async () => { - const res = await transformAsync( - src, - { plugins: [ - ["@babel/plugin-syntax-typescript", { isTSX: true }], - "./src/index.cjs" - ] } - ) - - console.log(res.code) -})() + +const ArrowComp: Component = ({ a, b, c, ...other }) =>
{a} {b} {c} {other.d}
+ +const FunctionExpressionComp: Component = function ({ a, b, c, ...other }) { return
{a} {b} {c} {other.d}
; } +`; + +(async () => { + const resTS = await transformAsync( + typeScriptSrc, + { + plugins: [ + ["@babel/plugin-syntax-typescript", { isTSX: true }], + ["./src/index.cjs", { uppercaseFuncNames: true }], + ] + } + ) + + console.log(resTS.code); + + const resJS = await transformAsync( + javascriptSrc, + { + plugins: [ + ["@babel/plugin-syntax-typescript", { isTSX: true }], + ["./src/index.cjs", { uppercaseFuncNames: true }], + ] + } + ); + + console.log(resJS.code); +})(); diff --git a/readme.md b/readme.md index 13535a0..f5e1cc3 100644 --- a/readme.md +++ b/readme.md @@ -174,6 +174,21 @@ const MyComponent = c(/* your component goes here. */) In this last example, `MyComponent` won't be transformed. +## uppercaseFuncNames option + +By setting this option to `true`, the plugin will assume function with names that start with an uppercase letter are components and will transform them accordingly. + +This option is disabled by default. + +Example: + +```javascript +const MyComponent = ({ a, b, c }) => { ... } +``` + +```javascript +function MyComponent({ a, b, c }) { ... } +``` ## Installation and Configuring Vite diff --git a/src/check-func/check-func.cjs b/src/check-func/check-func.cjs index 1d3fad3..168cdbc 100644 --- a/src/check-func/check-func.cjs +++ b/src/check-func/check-func.cjs @@ -1,6 +1,38 @@ const { checkFuncAnnotation } = require("./check-func-annotation.cjs") +/** + * Checks if a function node has a name that starts with an uppercase character + * + * @param {Object} opts + * @param {NodePath} path + * + * @returns {boolean} + */ +const checkFuncStartsWithUppercase = (opts, path) => { + if (!opts.uppercaseFuncNames) { return false } + + let funcName; + switch (path.type) { + case "FunctionDeclaration": + funcName = path.node.id.name + break; + case "FunctionExpression": + funcName = path.parent.id?.name; + break; + case "ArrowFunctionExpression": + funcName = path.parent.id?.name + break; + default: + return false; + } + + if (!funcName) { return false } + + return (funcName[0] === funcName[0].toUpperCase()); +} + + /** * Check that the first param ( `props` ) exists and is an object pattern. * @@ -25,21 +57,25 @@ const checkFirstParam = path => { /** * Checks if the visited function needs to be transformed (if the function is annotated - * and and the first argument is destructured). + * and the first argument is destructured). * If the function is annotated with a CTF, mark it in the state. */ const checkFunc = (funcPath, opts, state) => { // Make sure the function wasn't already transformed. if (funcPath.transformed) return false - if (!checkFuncAnnotation(opts, funcPath, state)) return { funcAnnotation: false } + if (!checkFuncAnnotation(opts, funcPath, state)) { + if (!checkFuncStartsWithUppercase(opts, funcPath, state)) { + return { funcAnnotation: false } + } + } - if (!checkFirstParam(funcPath)) return { + if (!checkFirstParam(funcPath)) return { funcAnnotation: true, propDestructuring: false } - return { + return { funcAnnotation: true, propDestructuring: true } diff --git a/src/utils.cjs b/src/utils.cjs index dd6e64e..985a20f 100644 --- a/src/utils.cjs +++ b/src/utils.cjs @@ -4,7 +4,7 @@ const { types } = require("@babel/core") module.exports.getDestructredProperties = funcPath => { const firstParam = funcPath.node.params[0] - const objectPattern = firstParam.type == "AssignmentPattern" + const objectPattern = firstParam.type === "AssignmentPattern" ? firstParam.left : firstParam diff --git a/tests/check-func-capital-letter.test.js b/tests/check-func-capital-letter.test.js new file mode 100644 index 0000000..c2e3984 --- /dev/null +++ b/tests/check-func-capital-letter.test.js @@ -0,0 +1,78 @@ +import { test } from 'uvu' +import * as assert from 'uvu/assert' +import babelParser from '@babel/parser' +import babelTraverse from '@babel/traverse' +import { checkFunc } from '../src/check-func/check-func.cjs' + +const traverse = babelTraverse.default + + +test('checkFunc', () => { + const assertCheckFunc = (code, expects, msg, uppercaseFuncNames = true) => { + const ast = babelParser.parse(code, { sourceType: "module" }) + + traverse(ast, { + Function: path => { + assert.equal(checkFunc(path, { uppercaseFuncNames }, {}), expects, msg) + path.transformed = true + } + }) + } + + const code7 = /*javascript*/` + const ArrowComp = ({ someProp }) => {} + ` + assertCheckFunc( + code7, + { funcAnnotation: false }, + "Returns false when uppercaseFuncNames is false even though the function starts with a capital letter.", + false, + ) + + const code1 = /*javascript*/` + const ArrowComp = ({ someProp }) => {} + ` + assertCheckFunc( + code1, + { funcAnnotation: true, propDestructuring: true }, + "Returns true for an arrow function that starts with a capital letter." + ) + + const code5 = /*javascript*/` + function FuncComp({ someProp }) {} + ` + assertCheckFunc( + code5, + { funcAnnotation: true, propDestructuring: true }, + "Returns true for a function that starts with a capital letter." + ) + + const code6 = /*javascript*/` + const ExprComp = function({ someProp }) {} + ` + assertCheckFunc( + code6, + { funcAnnotation: true, propDestructuring: true }, + "Returns true for a function expression that starts with a capital letter." + ) + + const code2 = /*javascript*/` + const notAComp = ({ someProp }) => {} + ` + assertCheckFunc( + code2, + { funcAnnotation: false }, + "Returns false for a camelCase function." + ) + + const code4 = /*javascript*/` + const ArrowComp = (props => {}); + ` + assertCheckFunc( + code4, + { funcAnnotation: true, propDestructuring: false }, + "Returns false for a function with no prop destructuring." + ) +}) + +test.run()