From 24461aa70743c898452362258d58da5799519a30 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Fri, 17 Jun 2022 14:18:19 +0800 Subject: [PATCH 1/7] feat: add x-if condition --- .gitignore | 2 + __tests__/usage.js | 67 +++++++++++++++++-- lib/index.d.ts | 5 -- lib/index.js | 29 -------- package.json | 1 + src/index.ts | 162 ++++++++++++++++++++++++++++++++++++++++----- src/utils.ts | 107 ++++++++++++++++++++++++++++++ 7 files changed, 315 insertions(+), 58 deletions(-) delete mode 100644 lib/index.d.ts delete mode 100644 lib/index.js create mode 100644 src/utils.ts diff --git a/.gitignore b/.gitignore index ae361ca..81ea095 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .idea +lib + # Logs logs *.log diff --git a/__tests__/usage.js b/__tests__/usage.js index 7648680..ed6854e 100644 --- a/__tests__/usage.js +++ b/__tests__/usage.js @@ -1,11 +1,64 @@ const swc = require('@swc/core') const path = require('path') -const ConsoleStripper = require(path.join(__dirname, '../lib/index.js')).default; +const JSXConditionTransformPlugin = require(path.join(__dirname, '../lib/index.js')).default; -it('should strip console call', () => { - const output = swc.transformSync(`console.log('Foo')`, { - plugin: (m) => (new ConsoleStripper()).visitModule(m), - }); +it('should convert jsx x-if condition', () => { - expect(output.code.replace(/\n/g, '')).toBe('void 0;') -}) + let input = `import { createElement } from 'react'; + +function Foo(props) { + return ( + + First + + ) +}`; + + const transformedOutput = swc.transformSync(input, { + jsc: { + parser: { + jsx: true + }, + }, + plugin: JSXConditionTransformPlugin + }); + + const output = `function _extends() { + _extends = Object.assign || function(target) { + for(var i = 1; i < arguments.length; i++){ + var source = arguments[i]; + for(var key in source){ + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + return target; + }; + return _extends.apply(this, arguments); +} +import { createCondition as __create_condition__ } from "babel-runtime-jsx-plus"; +import { createElement } from "react"; +function Foo(props) { + return __create_condition__([ + function() { + return true; + }, + function() { + return React.createElement(View, _extends({}, props, { + className: "container" + }), __create_condition__([ + function() { + return condition; + }, + function() { + return React.createElement(View, null, "First"); + } + ])); + } + ]); +} +`; + + expect(transformedOutput.code).toBe(output); +}); diff --git a/lib/index.d.ts b/lib/index.d.ts deleted file mode 100644 index 0d7328f..0000000 --- a/lib/index.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { CallExpression, Expression } from '@swc/core'; -import Visitor from '@swc/core/Visitor'; -export default class ConsoleStripper extends Visitor { - visitCallExpression(e: CallExpression): Expression; -} diff --git a/lib/index.js b/lib/index.js deleted file mode 100644 index 4971725..0000000 --- a/lib/index.js +++ /dev/null @@ -1,29 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const Visitor_1 = __importDefault(require("@swc/core/Visitor")); -class ConsoleStripper extends Visitor_1.default { - visitCallExpression(e) { - if (e.callee.type !== 'MemberExpression') { - return e; - } - if (e.callee.object.type === 'Identifier' && e.callee.object.value === 'console') { - if (e.callee.property.type === 'Identifier') { - return { - type: "UnaryExpression", - span: e.span, - operator: 'void', - argument: { - type: 'NumericLiteral', - span: e.span, - value: 0 - } - }; - } - } - return e; - } -} -exports.default = ConsoleStripper; diff --git a/package.json b/package.json index c68b3c1..5832bad 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "lib/index.js", "scripts": { "build": "tsc -d", + "build:watch": "tsc -d --watch", "test": "jest" }, "types": "./lib/index.d.ts", diff --git a/src/index.ts b/src/index.ts index 70bf835..c3e3eec 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,27 +1,155 @@ -import {CallExpression, Expression} from '@swc/core'; -import Visitor from '@swc/core/Visitor' +import { + Expression, + JSXElement, Program, Statement, +} from '@swc/core'; +import Visitor from "@swc/core/Visitor"; +import { + ExprOrSpread +} from "@swc/core/types"; +import { + buildArrayExpression, + buildArrowFunctionExpression, buildCallExpression, buildIdentifier, buildImportDeclaration, + buildJSXElement, + buildJSXExpressionContainer, buildNamedImportSpecifier, buildNullLiteral, buildStringLiteral +} from "./utils"; -export default class ConsoleStripper extends Visitor { - visitCallExpression(e: CallExpression): Expression { - if (e.callee.type !== 'MemberExpression') { - return e; +enum JSXConditionType { + if = 'x-if', + else = 'x-else', + elseif = 'x-elseif' +} + +function isJSXCondition(n: JSXElement) { + let opening = n.opening; + let openingAttributes = opening.attributes; + + if (openingAttributes) { + for (let attribute of openingAttributes) { + if (attribute.type === 'JSXAttribute' && attribute.name.type === 'Identifier') { + switch (attribute.name.value) { + case JSXConditionType.if: + case JSXConditionType.else: + case JSXConditionType.elseif: { + return true; + } + } + } } + } + return false; +} + +type JSXCondition = { + type: 'x-if' | 'x-elseif' | 'x-else', + expression?: Expression; +} - if (e.callee.object.type === 'Identifier' && e.callee.object.value === 'console') { - if (e.callee.property.type === 'Identifier') { - return { - type: "UnaryExpression", - span: e.span, - operator: 'void', - argument: { - type: 'NumericLiteral', - span: e.span, - value: 0 +function getJSXCondition(n: JSXElement): JSXCondition | undefined { + let opening = n.opening; + let openingAttributes = opening.attributes; + if (!openingAttributes) return undefined; + + for (let attribute of openingAttributes) { + if (attribute.type === 'JSXAttribute' && attribute.name.type === 'Identifier') { + switch (attribute.name.value) { + case JSXConditionType.if: + case JSXConditionType.else: + case JSXConditionType.elseif: { + if (attribute.value?.type === 'JSXExpressionContainer') { + return { + type: attribute.name.value, + expression: attribute.value.expression + }; + } + if (attribute.value === null) { + return { + type: attribute.name.value, + expression: buildNullLiteral() + } } } } } + } + + return undefined; +} - return e +function JSXConditionToStandard(n: JSXElement) { + let openingAttributes = n.opening.attributes; + + if (openingAttributes) { + openingAttributes = openingAttributes.filter((attribute) => { + if (attribute.type === 'JSXAttribute' && attribute.name.type === 'Identifier') { + switch (attribute.name.value) { + case JSXConditionType.if: + case JSXConditionType.else: + case JSXConditionType.elseif: { + return false; + } + } + } + return true; + }); } + return buildJSXElement({ + ...n.opening, + attributes: openingAttributes + }, n.children, n.closing) +} + + +function transformJSXCondition(n: JSXElement, isChild: boolean): JSXElement { + n.children = n.children.map((n) => { + if (n.type === 'JSXElement') { + return transformJSXCondition(n, true); + } + return n; + }); + + if (!isJSXCondition(n)) { + return n; + } + + let condition = getJSXCondition(n)!; + + let elements: ExprOrSpread[] = [ + { + expression: buildArrowFunctionExpression([], getJSXCondition(n)!.expression!) + }, + { + expression: buildArrowFunctionExpression([], JSXConditionToStandard(n)) + } + ]; + + let body = buildCallExpression(buildIdentifier('__create_condition__', false), [ + { + expression: buildArrayExpression(elements) + } + ]) as any; + + return isChild ? buildJSXExpressionContainer(body) : body; +} + +class JSXConditionTransformer extends Visitor { + visitJSXElement(n: JSXElement): JSXElement { + if (isJSXCondition(n)) { + return transformJSXCondition(n, false); + } + + return n; + } +} + +export default function JSXConditionTransformPlugin(m: Program): Program { + let result = new JSXConditionTransformer().visitProgram(m); + let babelImport = buildImportDeclaration([ + buildNamedImportSpecifier( + buildIdentifier('__create_condition__', false), + buildIdentifier('createCondition', false) + ) + ], buildStringLiteral('babel-runtime-jsx-plus')); + result.body.unshift(babelImport as any); + + return result; } diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..196f410 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,107 @@ +import { + ArrowFunctionExpression, + CallExpression, + Expression, Identifier, ImportDeclaration, + JSXElement, + JSXExpressionContainer, + NullLiteral +} from "@swc/core"; +import { + Argument, + ArrayExpression, BlockStatement, + ExprOrSpread, + HasSpan, Import, ImportSpecifier, + JSXClosingElement, + JSXElementChild, + JSXOpeningElement, NamedImportSpecifier, + Node, Pattern, StringLiteral, Super, TsTypeParameterInstantiation +} from "@swc/core/types"; + +export function buildBaseExpression(other: any): Node & HasSpan & T { + return { + ...other, + span: { + start: 0, + end: 0, + ctxt: 0 + }, + } +} + +export function buildArrayExpression(elements: (ExprOrSpread | undefined)[]): ArrayExpression { + return buildBaseExpression({ + type: 'ArrayExpression', + elements: elements + }); +} + +export function buildJSXElement(opening: JSXOpeningElement, children: JSXElementChild[], closing?: JSXClosingElement): JSXElement { + return buildBaseExpression({ + type: 'JSXElement', + opening: opening, + children: children, + closing: closing + }); +} + +export function buildArrowFunctionExpression(params: Pattern[], body: BlockStatement | Expression): ArrowFunctionExpression { + return buildBaseExpression({ + type: 'ArrowFunctionExpression', + params: params, + body: body, + async: false, + generator: false + }); +} + +export function buildNullLiteral(): NullLiteral { + return buildBaseExpression({ + type: 'NullLiteral' + }); +} + +export function buildJSXExpressionContainer(expression: Expression): JSXExpressionContainer { + return buildBaseExpression({ + type: 'JSXExpressionContainer', + expression: expression + }); +} + +export function buildImportDeclaration(specifiers: ImportSpecifier[], source: StringLiteral): ImportDeclaration { + return buildBaseExpression({ + type: 'ImportDeclaration', + specifiers: specifiers, + source: source + }); +} + +export function buildStringLiteral(value: string): StringLiteral { + return buildBaseExpression({ + type: 'StringLiteral', + value: value + }); +} + +export function buildNamedImportSpecifier(local: Identifier, imported: Identifier | null): NamedImportSpecifier { + return buildBaseExpression({ + type: 'ImportSpecifier', + local: local, + imported: imported + }); +} + +export function buildCallExpression(callee: Expression | Super | Import, args: Argument[]): CallExpression { + return buildBaseExpression({ + type: 'CallExpression', + callee: callee, + arguments: args + }) +} + +export function buildIdentifier(name: string, optional: boolean): Identifier { + return buildBaseExpression({ + type: 'Identifier', + value: name, + optional: optional + }) +} From e64684c0f3c8498eb1fd9365b9abd10ecea2d32d Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Fri, 17 Jun 2022 14:20:52 +0800 Subject: [PATCH 2/7] beta: 0.1.0-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5832bad..e859b47 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swc-plugin-transform-jsx-condition", - "version": "0.1.0", + "version": "0.1.0-beta.1", "description": "Support of transform jsx condition directive based on SWC", "main": "lib/index.js", "scripts": { From 432988610be647e6923aeca9c10ac0a2783f8638 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Fri, 17 Jun 2022 17:34:07 +0800 Subject: [PATCH 3/7] feat: auto test --- __tests__/__fixtures__/x-if/actual.js | 9 +++ __tests__/__fixtures__/x-if/expected.js | 35 ++++++++++++ __tests__/usage.js | 75 +++++++------------------ jest.config.js | 8 +++ package.json | 2 +- 5 files changed, 72 insertions(+), 57 deletions(-) create mode 100644 __tests__/__fixtures__/x-if/actual.js create mode 100644 __tests__/__fixtures__/x-if/expected.js create mode 100644 jest.config.js diff --git a/__tests__/__fixtures__/x-if/actual.js b/__tests__/__fixtures__/x-if/actual.js new file mode 100644 index 0000000..03fdcb4 --- /dev/null +++ b/__tests__/__fixtures__/x-if/actual.js @@ -0,0 +1,9 @@ +import { createElement } from 'react'; + +function Foo(props) { + return ( + + First + + ) +} diff --git a/__tests__/__fixtures__/x-if/expected.js b/__tests__/__fixtures__/x-if/expected.js new file mode 100644 index 0000000..b93df9e --- /dev/null +++ b/__tests__/__fixtures__/x-if/expected.js @@ -0,0 +1,35 @@ +function _extends() { + _extends = Object.assign || function(target) { + for(var i = 1; i < arguments.length; i++){ + var source = arguments[i]; + for(var key in source){ + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + return target; + }; + return _extends.apply(this, arguments); +} +import { createCondition as __create_condition__ } from "babel-runtime-jsx-plus"; +import { createElement } from "react"; +function Foo(props) { + return __create_condition__([ + function() { + return true; + }, + function() { + return React.createElement(View, _extends({}, props, { + className: "container" + }), __create_condition__([ + function() { + return condition; + }, + function() { + return React.createElement(View, null, "First"); + } + ])); + } + ]); +} diff --git a/__tests__/usage.js b/__tests__/usage.js index ed6854e..788e61e 100644 --- a/__tests__/usage.js +++ b/__tests__/usage.js @@ -1,64 +1,27 @@ const swc = require('@swc/core') const path = require('path') +const fs = require('fs'); const JSXConditionTransformPlugin = require(path.join(__dirname, '../lib/index.js')).default; -it('should convert jsx x-if condition', () => { +describe('', () => { + const fixturesDir = path.join(__dirname, '__fixtures__'); + fs.readdirSync(fixturesDir).map((caseName) => { + it(`should ${caseName.split('-').join(' ')}`, () => { + const fixtureDir = path.join(fixturesDir, caseName); + const actualPath = path.join(fixtureDir, 'actual.js'); + const actualCode = fs.readFileSync(actualPath, {encoding: 'utf-8'}); + const expectedCode = fs.readFileSync(path.join(fixtureDir, 'expected.js'), { encoding: 'utf-8' }); - let input = `import { createElement } from 'react'; - -function Foo(props) { - return ( - - First - - ) -}`; - - const transformedOutput = swc.transformSync(input, { - jsc: { - parser: { - jsx: true - }, - }, - plugin: JSXConditionTransformPlugin - }); - - const output = `function _extends() { - _extends = Object.assign || function(target) { - for(var i = 1; i < arguments.length; i++){ - var source = arguments[i]; - for(var key in source){ - if (Object.prototype.hasOwnProperty.call(source, key)) { - target[key] = source[key]; - } - } - } - return target; - }; - return _extends.apply(this, arguments); -} -import { createCondition as __create_condition__ } from "babel-runtime-jsx-plus"; -import { createElement } from "react"; -function Foo(props) { - return __create_condition__([ - function() { - return true; + const transformedOutput = swc.transformSync(actualCode, { + jsc: { + parser: { + jsx: true + }, }, - function() { - return React.createElement(View, _extends({}, props, { - className: "container" - }), __create_condition__([ - function() { - return condition; - }, - function() { - return React.createElement(View, null, "First"); - } - ])); - } - ]); -} -`; + plugin: JSXConditionTransformPlugin + }); - expect(transformedOutput.code).toBe(output); + expect(transformedOutput.code.trim()).toBe(expectedCode.trim()); + }); + }); }); diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..41a1d66 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,8 @@ +// Sync object +/** @type {import('@jest/types').Config.InitialOptions} */ +const config = { + verbose: true, + testMatch: ['!**/__fixtures__/**', '**/__tests__/**/*.js'] +}; + +module.exports = config; diff --git a/package.json b/package.json index e859b47..20eaa2d 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "typescript": "^4.7.3" }, "dependencies": {}, - "author": "andycall", + "author": "jsx-plus ", "license": "MIT", "bugs": { "url": "https://github.com/jsx-plus/swc-plugin-transform-jsx-condition/issues" From 07b125d71f6b0fd010b2470c31f5dea940c07a3d Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Mon, 20 Jun 2022 20:53:52 +0800 Subject: [PATCH 4/7] feat: add x-else and x-elseif --- __tests__/__fixtures__/x-if-else/actual.js | 11 ++ __tests__/__fixtures__/x-if-else/expected.js | 0 __tests__/usage.js | 63 +++++++---- src/index.ts | 108 ++++++++++++++++--- src/utils.ts | 19 +++- 5 files changed, 164 insertions(+), 37 deletions(-) create mode 100644 __tests__/__fixtures__/x-if-else/actual.js create mode 100644 __tests__/__fixtures__/x-if-else/expected.js diff --git a/__tests__/__fixtures__/x-if-else/actual.js b/__tests__/__fixtures__/x-if-else/actual.js new file mode 100644 index 0000000..1f8f0eb --- /dev/null +++ b/__tests__/__fixtures__/x-if-else/actual.js @@ -0,0 +1,11 @@ +import { createElement } from 'react'; + +function Foo(props) { + return ( + + First + Second + Third + + ) +} diff --git a/__tests__/__fixtures__/x-if-else/expected.js b/__tests__/__fixtures__/x-if-else/expected.js new file mode 100644 index 0000000..e69de29 diff --git a/__tests__/usage.js b/__tests__/usage.js index 788e61e..e43e67b 100644 --- a/__tests__/usage.js +++ b/__tests__/usage.js @@ -3,25 +3,50 @@ const path = require('path') const fs = require('fs'); const JSXConditionTransformPlugin = require(path.join(__dirname, '../lib/index.js')).default; -describe('', () => { - const fixturesDir = path.join(__dirname, '__fixtures__'); - fs.readdirSync(fixturesDir).map((caseName) => { - it(`should ${caseName.split('-').join(' ')}`, () => { - const fixtureDir = path.join(fixturesDir, caseName); - const actualPath = path.join(fixtureDir, 'actual.js'); - const actualCode = fs.readFileSync(actualPath, {encoding: 'utf-8'}); - const expectedCode = fs.readFileSync(path.join(fixtureDir, 'expected.js'), { encoding: 'utf-8' }); +// describe('', () => { +// const fixturesDir = path.join(__dirname, '__fixtures__'); +// fs.readdirSync(fixturesDir).map((caseName) => { +// it(`should ${caseName.split('-').join(' ')}`, () => { +// const fixtureDir = path.join(fixturesDir, caseName); +// const actualPath = path.join(fixtureDir, 'actual.js'); +// const actualCode = fs.readFileSync(actualPath, {encoding: 'utf-8'}); +// const expectedCode = fs.readFileSync(path.join(fixtureDir, 'expected.js'), { encoding: 'utf-8' }); +// +// const transformedOutput = swc.transformSync(actualCode, { +// jsc: { +// parser: { +// jsx: true +// }, +// }, +// plugin: JSXConditionTransformPlugin +// }); +// +// expect(transformedOutput.code.trim()).toBe(expectedCode.trim()); +// }); +// }); +// }); - const transformedOutput = swc.transformSync(actualCode, { - jsc: { - parser: { - jsx: true - }, - }, - plugin: JSXConditionTransformPlugin - }); - expect(transformedOutput.code.trim()).toBe(expectedCode.trim()); - }); +const fixturesDir = path.join(__dirname, '__fixtures__'); +const caseName = 'x-if-else'; +// fs.readdirSync(fixturesDir).map((caseName) => { + // it(`should ${caseName.split('-').join(' ')}`, () => { + const fixtureDir = path.join(fixturesDir, caseName); + const actualPath = path.join(fixtureDir, 'actual.js'); + const actualCode = fs.readFileSync(actualPath, {encoding: 'utf-8'}); + const expectedCode = fs.readFileSync(path.join(fixtureDir, 'expected.js'), { encoding: 'utf-8' }); + + const transformedOutput = swc.transformSync(actualCode, { + jsc: { + parser: { + jsx: true + }, + }, + plugin: JSXConditionTransformPlugin }); -}); + + console.log(transformedOutput.code); + + // expect(transformedOutput.code.trim()).toBe(expectedCode.trim()); + // }); +// }); diff --git a/src/index.ts b/src/index.ts index c3e3eec..3b40364 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,16 +1,16 @@ import { Expression, - JSXElement, Program, Statement, + JSXElement, JSXText, Program, } from '@swc/core'; import Visitor from "@swc/core/Visitor"; import { - ExprOrSpread + ExprOrSpread, JSXElementChild } from "@swc/core/types"; import { buildArrayExpression, - buildArrowFunctionExpression, buildCallExpression, buildIdentifier, buildImportDeclaration, + buildArrowFunctionExpression, buildBooleanLiteral, buildCallExpression, buildIdentifier, buildImportDeclaration, buildJSXElement, - buildJSXExpressionContainer, buildNamedImportSpecifier, buildNullLiteral, buildStringLiteral + buildJSXExpressionContainer, buildJSXText, buildNamedImportSpecifier, buildNullLiteral, buildStringLiteral } from "./utils"; enum JSXConditionType { @@ -40,7 +40,7 @@ function isJSXCondition(n: JSXElement) { } type JSXCondition = { - type: 'x-if' | 'x-elseif' | 'x-else', + type: JSXConditionType; expression?: Expression; } @@ -99,12 +99,12 @@ function JSXConditionToStandard(n: JSXElement) { } -function transformJSXCondition(n: JSXElement, isChild: boolean): JSXElement { - n.children = n.children.map((n) => { - if (n.type === 'JSXElement') { - return transformJSXCondition(n, true); +function transformJSXCondition(n: JSXElement, currentList: JSXElementChild[], currentIndex: number): JSXElement | JSXText { + n.children = n.children.map((c, i) => { + if (c.type === 'JSXElement') { + return transformJSXCondition(c, n.children, i); } - return n; + return c; }); if (!isJSXCondition(n)) { @@ -112,29 +112,105 @@ function transformJSXCondition(n: JSXElement, isChild: boolean): JSXElement { } let condition = getJSXCondition(n)!; + if (condition.type === JSXConditionType.else || condition.type === JSXConditionType.elseif) { + // @ts-ignore + if (!n.__indented) { + return n; + } + + return buildJSXText(''); + } + + let elseIfJSXElement : JSXElement | null = null; + let elseJSXElement: JSXElement | null = null; + + let isRoot = currentIndex === -1; + if (!isRoot && condition.type === JSXConditionType.if) { + let indent = 1; + let nextSibling = currentList[currentIndex + indent]; + while (nextSibling && nextSibling.type != 'JSXElement') { + indent++; + nextSibling = currentList[currentIndex + indent]; + } + + if (nextSibling) { + let nextJSXKind = getJSXCondition(nextSibling); + if (nextJSXKind && nextJSXKind.type === JSXConditionType.elseif) { + elseIfJSXElement = nextSibling; + indent++; + nextSibling = currentList[currentIndex + indent]; + while (nextSibling && nextSibling.type != 'JSXElement') { + indent++; + nextSibling = currentList[currentIndex + indent]; + } + + if (nextSibling) { + elseJSXElement = nextSibling; + } + } else if (nextJSXKind && nextJSXKind.type === JSXConditionType.else) { + elseJSXElement = nextSibling; + } + } + } let elements: ExprOrSpread[] = [ { - expression: buildArrowFunctionExpression([], getJSXCondition(n)!.expression!) + expression: buildArrayExpression([ + { + expression: buildArrowFunctionExpression([], condition.expression!) + }, + { + expression: buildArrowFunctionExpression([], JSXConditionToStandard(n)) + } + ]) }, - { - expression: buildArrowFunctionExpression([], JSXConditionToStandard(n)) - } ]; + if (elseIfJSXElement) { + // @ts-ignore + elseIfJSXElement.__indented = true; + + elements.push({ + expression: buildArrayExpression([ + { + expression: buildArrowFunctionExpression([], getJSXCondition(elseIfJSXElement)!.expression!) + }, + { + expression: buildArrowFunctionExpression([], JSXConditionToStandard(elseIfJSXElement)) + } + ]) + }); + } + + if (elseJSXElement) { + // @ts-ignore + elseJSXElement.__indented = true; + elements.push({ + expression: buildArrayExpression([ + { + expression: buildArrowFunctionExpression([], buildBooleanLiteral(true)) + }, + { + expression: buildArrowFunctionExpression([], JSXConditionToStandard(elseJSXElement)) + } + ]) + }); + } + let body = buildCallExpression(buildIdentifier('__create_condition__', false), [ { expression: buildArrayExpression(elements) } ]) as any; - return isChild ? buildJSXExpressionContainer(body) : body; + + return isRoot ? body : buildJSXExpressionContainer(body); } class JSXConditionTransformer extends Visitor { visitJSXElement(n: JSXElement): JSXElement { if (isJSXCondition(n)) { - return transformJSXCondition(n, false); + return transformJSXCondition(n, [], -1) as JSXElement; } return n; diff --git a/src/utils.ts b/src/utils.ts index 196f410..dd7fb29 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,9 +1,9 @@ import { - ArrowFunctionExpression, + ArrowFunctionExpression, BooleanLiteral, CallExpression, Expression, Identifier, ImportDeclaration, JSXElement, - JSXExpressionContainer, + JSXExpressionContainer, JSXText, NullLiteral } from "@swc/core"; import { @@ -82,6 +82,21 @@ export function buildStringLiteral(value: string): StringLiteral { }); } +export function buildJSXText(value: ''): JSXText { + return buildBaseExpression({ + type: 'JSXText', + value: value, + raw: value + }) +} + +export function buildBooleanLiteral(value: boolean) { + return buildBaseExpression({ + type: 'BooleanLiteral', + value: value + }); +} + export function buildNamedImportSpecifier(local: Identifier, imported: Identifier | null): NamedImportSpecifier { return buildBaseExpression({ type: 'ImportSpecifier', From 87e6649546398f644c128bf91465400788d7318c Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Wed, 22 Jun 2022 16:45:09 +0800 Subject: [PATCH 5/7] feat: done --- __tests__/__fixtures__/x-if-else/actual.js | 4 +- __tests__/__fixtures__/x-if-else/expected.js | 67 ++++++++++++ __tests__/__fixtures__/x-if/expected.js | 35 ++++--- __tests__/usage.js | 63 ++++-------- src/index.ts | 102 +++++++------------ 5 files changed, 145 insertions(+), 126 deletions(-) diff --git a/__tests__/__fixtures__/x-if-else/actual.js b/__tests__/__fixtures__/x-if-else/actual.js index 1f8f0eb..9509430 100644 --- a/__tests__/__fixtures__/x-if-else/actual.js +++ b/__tests__/__fixtures__/x-if-else/actual.js @@ -4,8 +4,8 @@ function Foo(props) { return ( First - Second - Third + FirstSecondThird + Third ) } diff --git a/__tests__/__fixtures__/x-if-else/expected.js b/__tests__/__fixtures__/x-if-else/expected.js index e69de29..2758368 100644 --- a/__tests__/__fixtures__/x-if-else/expected.js +++ b/__tests__/__fixtures__/x-if-else/expected.js @@ -0,0 +1,67 @@ +function _extends() { + _extends = Object.assign || function(target) { + for(var i = 1; i < arguments.length; i++){ + var source = arguments[i]; + for(var key in source){ + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + return target; + }; + return _extends.apply(this, arguments); +} +import { createCondition as __create_condition__ } from "babel-runtime-jsx-plus"; +import { createElement } from "react"; +function Foo(props) { + return __create_condition__([ + [ + function() { + return true; + }, + function() { + return React.createElement(View, _extends({}, props, { + className: "container" + }), __create_condition__([ + [ + function() { + return condition; + }, + function() { + return React.createElement(View, null, "First"); + } + ] + ]), __create_condition__([ + [ + function() { + return condition; + }, + function() { + return React.createElement(View, null, "First"); + } + ], + [ + function() { + return another; + }, + function() { + return React.createElement(View, null, "Second"); + } + ], + [ + function() { + return true; + }, + function() { + return React.createElement(View, null, "Third"); + } + ] + ]), /*#__PURE__*/ React.createElement(View, { + "x-else": true + }, "Third")); + } + ] + ]); +} + diff --git a/__tests__/__fixtures__/x-if/expected.js b/__tests__/__fixtures__/x-if/expected.js index b93df9e..f5a8160 100644 --- a/__tests__/__fixtures__/x-if/expected.js +++ b/__tests__/__fixtures__/x-if/expected.js @@ -16,20 +16,25 @@ import { createCondition as __create_condition__ } from "babel-runtime-jsx-plus" import { createElement } from "react"; function Foo(props) { return __create_condition__([ - function() { - return true; - }, - function() { - return React.createElement(View, _extends({}, props, { - className: "container" - }), __create_condition__([ - function() { - return condition; - }, - function() { - return React.createElement(View, null, "First"); - } - ])); - } + [ + function() { + return true; + }, + function() { + return React.createElement(View, _extends({}, props, { + className: "container" + }), __create_condition__([ + [ + function() { + return condition; + }, + function() { + return React.createElement(View, null, "First"); + } + ] + ])); + } + ] ]); } + diff --git a/__tests__/usage.js b/__tests__/usage.js index e43e67b..788e61e 100644 --- a/__tests__/usage.js +++ b/__tests__/usage.js @@ -3,50 +3,25 @@ const path = require('path') const fs = require('fs'); const JSXConditionTransformPlugin = require(path.join(__dirname, '../lib/index.js')).default; -// describe('', () => { -// const fixturesDir = path.join(__dirname, '__fixtures__'); -// fs.readdirSync(fixturesDir).map((caseName) => { -// it(`should ${caseName.split('-').join(' ')}`, () => { -// const fixtureDir = path.join(fixturesDir, caseName); -// const actualPath = path.join(fixtureDir, 'actual.js'); -// const actualCode = fs.readFileSync(actualPath, {encoding: 'utf-8'}); -// const expectedCode = fs.readFileSync(path.join(fixtureDir, 'expected.js'), { encoding: 'utf-8' }); -// -// const transformedOutput = swc.transformSync(actualCode, { -// jsc: { -// parser: { -// jsx: true -// }, -// }, -// plugin: JSXConditionTransformPlugin -// }); -// -// expect(transformedOutput.code.trim()).toBe(expectedCode.trim()); -// }); -// }); -// }); +describe('', () => { + const fixturesDir = path.join(__dirname, '__fixtures__'); + fs.readdirSync(fixturesDir).map((caseName) => { + it(`should ${caseName.split('-').join(' ')}`, () => { + const fixtureDir = path.join(fixturesDir, caseName); + const actualPath = path.join(fixtureDir, 'actual.js'); + const actualCode = fs.readFileSync(actualPath, {encoding: 'utf-8'}); + const expectedCode = fs.readFileSync(path.join(fixtureDir, 'expected.js'), { encoding: 'utf-8' }); + const transformedOutput = swc.transformSync(actualCode, { + jsc: { + parser: { + jsx: true + }, + }, + plugin: JSXConditionTransformPlugin + }); -const fixturesDir = path.join(__dirname, '__fixtures__'); -const caseName = 'x-if-else'; -// fs.readdirSync(fixturesDir).map((caseName) => { - // it(`should ${caseName.split('-').join(' ')}`, () => { - const fixtureDir = path.join(fixturesDir, caseName); - const actualPath = path.join(fixtureDir, 'actual.js'); - const actualCode = fs.readFileSync(actualPath, {encoding: 'utf-8'}); - const expectedCode = fs.readFileSync(path.join(fixtureDir, 'expected.js'), { encoding: 'utf-8' }); - - const transformedOutput = swc.transformSync(actualCode, { - jsc: { - parser: { - jsx: true - }, - }, - plugin: JSXConditionTransformPlugin + expect(transformedOutput.code.trim()).toBe(expectedCode.trim()); + }); }); - - console.log(transformedOutput.code); - - // expect(transformedOutput.code.trim()).toBe(expectedCode.trim()); - // }); -// }); +}); diff --git a/src/index.ts b/src/index.ts index 3b40364..6af3f88 100644 --- a/src/index.ts +++ b/src/index.ts @@ -114,88 +114,60 @@ function transformJSXCondition(n: JSXElement, currentList: JSXElementChild[], cu let condition = getJSXCondition(n)!; if (condition.type === JSXConditionType.else || condition.type === JSXConditionType.elseif) { // @ts-ignore - if (!n.__indented) { - return n; + if (n.__skip) { + return buildJSXText(''); } - return buildJSXText(''); + return n; } - let elseIfJSXElement : JSXElement | null = null; - let elseJSXElement: JSXElement | null = null; - let isRoot = currentIndex === -1; - if (!isRoot && condition.type === JSXConditionType.if) { - let indent = 1; - let nextSibling = currentList[currentIndex + indent]; - while (nextSibling && nextSibling.type != 'JSXElement') { - indent++; - nextSibling = currentList[currentIndex + indent]; - } - if (nextSibling) { - let nextJSXKind = getJSXCondition(nextSibling); - if (nextJSXKind && nextJSXKind.type === JSXConditionType.elseif) { - elseIfJSXElement = nextSibling; - indent++; - nextSibling = currentList[currentIndex + indent]; - while (nextSibling && nextSibling.type != 'JSXElement') { - indent++; - nextSibling = currentList[currentIndex + indent]; - } + type JSXConditionExpression = { + condition: Expression; + jsxElement: JSXElement; + }; - if (nextSibling) { - elseJSXElement = nextSibling; - } - } else if (nextJSXKind && nextJSXKind.type === JSXConditionType.else) { - elseJSXElement = nextSibling; - } - } - } - - let elements: ExprOrSpread[] = [ + let conditions: JSXConditionExpression[] = [ { - expression: buildArrayExpression([ - { - expression: buildArrowFunctionExpression([], condition.expression!) - }, - { - expression: buildArrowFunctionExpression([], JSXConditionToStandard(n)) - } - ]) - }, + condition: condition!.expression!, + jsxElement: n + } ]; - if (elseIfJSXElement) { - // @ts-ignore - elseIfJSXElement.__indented = true; - - elements.push({ - expression: buildArrayExpression([ - { - expression: buildArrowFunctionExpression([], getJSXCondition(elseIfJSXElement)!.expression!) - }, - { - expression: buildArrowFunctionExpression([], JSXConditionToStandard(elseIfJSXElement)) - } - ]) - }); - } + let continueSearch = true; + let indent = 1; + let nextJSXKind: JSXCondition | undefined; + do { + let nextSibling = currentList[currentIndex + indent]; + if (nextSibling && nextSibling.type === 'JSXText' && nextSibling.value.trim() === '') { + indent++; + } else if (nextSibling && nextSibling.type === 'JSXElement' && (nextJSXKind = getJSXCondition(nextSibling)) && nextJSXKind && nextJSXKind.type != JSXConditionType.if) { + conditions.push({ + condition: nextJSXKind.type === JSXConditionType.elseif ? getJSXCondition(nextSibling)!.expression! : buildBooleanLiteral(true), + jsxElement: nextSibling + }); + // @ts-ignore + nextSibling.__skip = true; + continueSearch = nextJSXKind.type === JSXConditionType.elseif; + indent++; + } else { + continueSearch = false; + } + } while (continueSearch); - if (elseJSXElement) { - // @ts-ignore - elseJSXElement.__indented = true; - elements.push({ + let elements: ExprOrSpread[] = conditions.map((con) => { + return { expression: buildArrayExpression([ { - expression: buildArrowFunctionExpression([], buildBooleanLiteral(true)) + expression: buildArrowFunctionExpression([], con.condition) }, { - expression: buildArrowFunctionExpression([], JSXConditionToStandard(elseJSXElement)) + expression: buildArrowFunctionExpression([], JSXConditionToStandard(con.jsxElement)) } ]) - }); - } + } + }); let body = buildCallExpression(buildIdentifier('__create_condition__', false), [ { From 6d0497aaf15f4c332c36a88086e4c8e703dedbbd Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Wed, 22 Jun 2022 16:46:03 +0800 Subject: [PATCH 6/7] chore: fix format --- src/index.ts | 6 +++--- src/utils.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/index.ts b/src/index.ts index 6af3f88..31973f1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,16 +2,16 @@ import { Expression, JSXElement, JSXText, Program, } from '@swc/core'; -import Visitor from "@swc/core/Visitor"; +import Visitor from '@swc/core/Visitor'; import { ExprOrSpread, JSXElementChild -} from "@swc/core/types"; +} from '@swc/core/types'; import { buildArrayExpression, buildArrowFunctionExpression, buildBooleanLiteral, buildCallExpression, buildIdentifier, buildImportDeclaration, buildJSXElement, buildJSXExpressionContainer, buildJSXText, buildNamedImportSpecifier, buildNullLiteral, buildStringLiteral -} from "./utils"; +} from './utils'; enum JSXConditionType { if = 'x-if', diff --git a/src/utils.ts b/src/utils.ts index dd7fb29..c508129 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -5,7 +5,7 @@ import { JSXElement, JSXExpressionContainer, JSXText, NullLiteral -} from "@swc/core"; +} from '@swc/core'; import { Argument, ArrayExpression, BlockStatement, @@ -15,7 +15,7 @@ import { JSXElementChild, JSXOpeningElement, NamedImportSpecifier, Node, Pattern, StringLiteral, Super, TsTypeParameterInstantiation -} from "@swc/core/types"; +} from '@swc/core/types'; export function buildBaseExpression(other: any): Node & HasSpan & T { return { From 084b68628401664a6b58283d9448c4cdc4779987 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Thu, 23 Jun 2022 17:57:38 +0800 Subject: [PATCH 7/7] fix: update deps --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 20eaa2d..5350161 100644 --- a/package.json +++ b/package.json @@ -14,11 +14,12 @@ "url": "git+https://github.com/jsx-plus/swc-plugin-transform-jsx-condition.git" }, "devDependencies": { - "@swc/core": "^1.2.203", "jest": "^24.9.0", "typescript": "^4.7.3" }, - "dependencies": {}, + "dependencies": { + "@swc/core": "^1.2.203" + }, "author": "jsx-plus ", "license": "MIT", "bugs": {