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()