Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into GH-2603
Browse files Browse the repository at this point in the history
  • Loading branch information
mmews committed Feb 20, 2024
2 parents 9fb84b9 + f2b2ce1 commit 6011bcd
Show file tree
Hide file tree
Showing 41 changed files with 1,107 additions and 405 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.eclipse.n4js.n4JS.VariableStatement;
import org.eclipse.n4js.n4JS.VariableStatementKeyword;
import org.eclipse.n4js.packagejson.PackageJsonProperties;
import org.eclipse.n4js.tooling.react.ReactHelper;
import org.eclipse.n4js.transpiler.Transformation;
import org.eclipse.n4js.transpiler.TransformationDependency.ExcludesAfter;
import org.eclipse.n4js.transpiler.TransformationDependency.ExcludesBefore;
Expand Down Expand Up @@ -100,7 +101,9 @@ public void transform() {
ImmutableListMultimap<TModule, ImportDeclaration> importDeclsPerImportedModule = FluentIterable
.from(getState().im.getScriptElements())
.filter(ImportDeclaration.class)
.filter(id -> !id.isBare()) // ignore bare imports
// ignore bare imports and special jsx import
.filter(id -> !id.isBare()
&& !ReactHelper.REACT_JSX_RUNTIME_NAME.equals(id.getModuleSpecifierAsText()))
.index(importDecl -> getState().info.getImportedModule(importDecl));

List<VariableStatement> varStmnts = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,32 @@
*/
package org.eclipse.n4js.transpiler.es.transform;

import static org.eclipse.n4js.tooling.react.ReactHelper.REACT_ELEMENT_PROPERTY_CHILDREN_NAME;
import static org.eclipse.n4js.tooling.react.ReactHelper.REACT_ELEMENT_PROPERTY_KEY_NAME;
import static org.eclipse.n4js.tooling.react.ReactHelper.REACT_JSXS_TRANSFORM_NAME;
import static org.eclipse.n4js.tooling.react.ReactHelper.REACT_JSX_RUNTIME_NAME;
import static org.eclipse.n4js.tooling.react.ReactHelper.REACT_JSX_TRANSFORM_NAME;
import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ArrLit;
import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._CallExpr;
import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._NULL;
import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._NamedImportSpecifier;
import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._ObjLit;
import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyAccessExpr;
import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertyNameValuePair;
import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._PropertySpread;
import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._StringLiteral;
import static org.eclipse.n4js.transpiler.TranspilerBuilderBlocks._TRUE;
import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter;
import static org.eclipse.xtext.xbase.lib.IterableExtensions.map;
import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.emf.common.util.EList;
import org.eclipse.n4js.n4JS.Expression;
import org.eclipse.n4js.n4JS.ImportDeclaration;
import org.eclipse.n4js.n4JS.JSXAbstractElement;
Expand All @@ -34,19 +46,19 @@
import org.eclipse.n4js.n4JS.JSXFragment;
import org.eclipse.n4js.n4JS.JSXPropertyAttribute;
import org.eclipse.n4js.n4JS.JSXSpreadAttribute;
import org.eclipse.n4js.n4JS.NamedImportSpecifier;
import org.eclipse.n4js.n4JS.NamespaceImportSpecifier;
import org.eclipse.n4js.n4JS.ObjectLiteral;
import org.eclipse.n4js.n4JS.ParameterizedCallExpression;
import org.eclipse.n4js.n4JS.PropertyAssignment;
import org.eclipse.n4js.n4JS.PropertyNameValuePair;
import org.eclipse.n4js.tooling.react.ReactHelper;
import org.eclipse.n4js.transpiler.Transformation;
import org.eclipse.n4js.transpiler.im.IdentifierRef_IM;
import org.eclipse.n4js.transpiler.im.ImFactory;
import org.eclipse.n4js.transpiler.im.Script_IM;
import org.eclipse.n4js.transpiler.im.SymbolTableEntry;
import org.eclipse.n4js.transpiler.im.SymbolTableEntryInternal;
import org.eclipse.n4js.transpiler.im.SymbolTableEntryOriginal;
import org.eclipse.n4js.ts.types.IdentifiableElement;
import org.eclipse.n4js.ts.types.TFunction;
import org.eclipse.n4js.ts.types.TModule;
import org.eclipse.n4js.utils.ResourceType;
import org.eclipse.xtext.EcoreUtil2;
Expand All @@ -65,13 +77,16 @@
* will be transformed to
*
* <pre>
* React.createElement('div', Object.assign({attr: "value"}));
* $jsv('div', {attr: "value"});
* </pre>
*/
public class JSXTransformation extends Transformation {
static final Map<String, String> ALIAS_MAP = Map.of(
REACT_JSX_TRANSFORM_NAME, "$" + REACT_JSX_TRANSFORM_NAME,
REACT_JSXS_TRANSFORM_NAME, "$" + REACT_JSXS_TRANSFORM_NAME);

private final Set<String> necessaryImports = new LinkedHashSet<>();
private SymbolTableEntryOriginal steForJsxBackendNamespace;
private SymbolTableEntryOriginal steForJsxBackendElementFactoryFunction;
private SymbolTableEntryOriginal steForJsxBackendFragmentComponent;

@Inject
Expand Down Expand Up @@ -124,24 +139,35 @@ public void transform() {
return; // this transformation is not applicable
}

// Transform JSXFragments and JSXElements
// note: we are passing 'true' to #collectNodes(), i.e. we are searching for nested elements
List<JSXAbstractElement> jsxAbstractElements = collectNodes(getState().im, JSXAbstractElement.class, true);
if (jsxAbstractElements.isEmpty()) {
// Nothing to transform
return;
}

steForJsxBackendNamespace = prepareImportOfJsxBackend();
steForJsxBackendElementFactoryFunction = prepareElementFactoryFunction();
steForJsxBackendNamespace = createImportOfJsxBackend(); // will be removed if obsolete
steForJsxBackendFragmentComponent = prepareFragmentComponent();

// note: we are passing 'true' to #collectNodes(), i.e. we are searching for nested elements
// Transform JSXFragments and JSXElements
for (JSXAbstractElement jsxElem : jsxAbstractElements) {
transformJSXAbstractElement(jsxElem);
}
createNecessaryImportsOfJsx();
}

private void createNecessaryImportsOfJsx() {
List<String> importSpecifierNames = new ArrayList<>(necessaryImports);
NamedImportSpecifier[] importSpecs = new NamedImportSpecifier[importSpecifierNames.size()];
for (int i = 0; i < importSpecifierNames.size(); i++) {
String isn = importSpecifierNames.get(i);
String alias = ALIAS_MAP.get(isn);
importSpecs[i] = _NamedImportSpecifier(isn, alias, true);
}
addNamedImport(REACT_JSX_RUNTIME_NAME, importSpecs);
}

private SymbolTableEntryOriginal prepareImportOfJsxBackend() {
private SymbolTableEntryOriginal createImportOfJsxBackend() {
TModule jsxBackendModule = reactHelper.getJsxBackendModule(getState().resource);
if (jsxBackendModule == null) {
throw new RuntimeException("cannot locate JSX backend for N4JSX resource " + getState().resource.getURI());
Expand Down Expand Up @@ -169,15 +195,6 @@ private SymbolTableEntryOriginal prepareImportOfJsxBackend() {
return addNamespaceImport(jsxBackendModule, reactHelper.getJsxBackendNamespaceName());
}

private SymbolTableEntryOriginal prepareElementFactoryFunction() {
TFunction elementFactoryFunction = reactHelper.getJsxBackendElementFactoryFunction(getState().resource);
if (elementFactoryFunction == null) {
throw new RuntimeException("cannot locate element factory function of JSX backend for N4JSX resource "
+ getState().resource.getURI());
}
return getSymbolTableEntryOriginal(elementFactoryFunction, true);
}

private SymbolTableEntryOriginal prepareFragmentComponent() {
IdentifiableElement fragmentComponent = reactHelper.getJsxBackendFragmentComponent(getState().resource);
if (fragmentComponent == null) {
Expand All @@ -202,90 +219,98 @@ private ParameterizedCallExpression convertJSXAbstractElement(JSXAbstractElement
if (elem instanceof JSXElement) {
JSXElement jsxElem = (JSXElement) elem;
args.add(getTagNameFromElement(jsxElem));
args.add(convertJSXAttributes(jsxElem.getJsxAttributes()));
} else {
args.add(convertJSXAttributes(jsxElem.getJsxAttributes(), elem.getJsxChildren()));
Expression keysValue = findKeysAttribute(jsxElem.getJsxAttributes());
if (keysValue != null) {
args.add(keysValue);
}
} else if (elem instanceof JSXFragment) {
args.add(_PropertyAccessExpr(steForJsxBackendNamespace, steForJsxBackendFragmentComponent));
args.add(_NULL());
args.add(convertJSXAttributes(Collections.emptyList(), elem.getJsxChildren()));
}
args.addAll(toList(map(elem.getJsxChildren(), child -> convertJSXChild(child))));

return _CallExpr(
_PropertyAccessExpr(steForJsxBackendNamespace, steForJsxBackendElementFactoryFunction),
args.toArray(new Expression[0]));
IdentifierRef_IM idRef = ImFactory.eINSTANCE.createIdentifierRef_IM();
String isn = elem.getJsxChildren().size() > 1 ? REACT_JSXS_TRANSFORM_NAME : REACT_JSX_TRANSFORM_NAME;
necessaryImports.add(isn);
String jsxAlias = ALIAS_MAP.get(isn);
idRef.setIdAsText(jsxAlias);
SymbolTableEntryInternal ste = getSymbolTableEntryInternal(idRef.getIdAsText(), true);
idRef.setId_IM(ste);
return _CallExpr(idRef, args.toArray(new Expression[0]));
}

private Expression convertJSXChild(JSXChild child) {
if (child instanceof JSXElement) {
return convertJSXAbstractElement((JSXElement) child);
}
if (child instanceof JSXFragment) {
return convertJSXAbstractElement((JSXFragment) child);
}
if (child instanceof JSXExpression) {
return ((JSXExpression) child).getExpression();
private Expression findKeysAttribute(EList<JSXAttribute> jsxAttributes) {
for (JSXAttribute attr : jsxAttributes) {
if (attr instanceof JSXPropertyAttribute) {
JSXPropertyAttribute pa = (JSXPropertyAttribute) attr;
// https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md#motivation
// notes that the key property will not be extracted from attributes
// at some time in the future
if (REACT_ELEMENT_PROPERTY_KEY_NAME.equals(pa.getPropertyAsText())) {
return pa.getJsxAttributeValue();
}
}
}
return null;
}

// Generate Object.assign({}, {foo, bar: "Hi"}, spr)
private Expression convertJSXAttributes(List<JSXAttribute> attrs) {
if (attrs.isEmpty()) {
return _NULL();
} else if (attrs.size() == 1 && attrs.get(0) instanceof JSXSpreadAttribute) {
// Generate {foo:foo, ...spread, bar: "Hi", children: []}
private Expression convertJSXAttributes(List<JSXAttribute> attrs, List<JSXChild> children) {
if (children.isEmpty() && attrs.isEmpty()) {
return _ObjLit();
}
if (children.isEmpty() && attrs.size() == 1 && attrs.get(0) instanceof JSXSpreadAttribute) {
// Special case: if only a single spread operator is passed, we pass it directly, e.g. spr instead of
// cloning with Object.assign.
// cloning.
return ((JSXSpreadAttribute) attrs.get(0)).getExpression();
} else {
}

List<PropertyAssignment> pas = new ArrayList<>();

List<Integer> spreadIndices = new ArrayList<>();
for (int idx = 0; idx < attrs.size(); idx++) {
if (attrs.get(idx) instanceof JSXSpreadAttribute) {
spreadIndices.add(idx);
for (JSXAttribute attr : attrs) {
if (attr instanceof JSXSpreadAttribute) {
JSXSpreadAttribute sAttr = (JSXSpreadAttribute) attr;
pas.add(_PropertySpread(sAttr.getExpression()));
} else if (attr instanceof JSXPropertyAttribute) {
JSXPropertyAttribute pAttr = (JSXPropertyAttribute) attr;
if (!children.isEmpty() && REACT_ELEMENT_PROPERTY_CHILDREN_NAME.equals(pAttr.getPropertyAsText())) {
continue;
}
}
// GHOLD-413: We have to make sure that the only properties locating next to each other are combined.
// Moreover, the order of properties as well as spread operators must be preserved!
List<PropertyNameValuePair> props = new ArrayList<>();
if (attrs.get(0) instanceof JSXSpreadAttribute) {
// The first attribute is a spread object, the target must be {}.
} else {
// Otherwise, the target is of the form {foo: true, bar: "Hi"}
int firstSpreadIndex = (!spreadIndices.isEmpty()) ? spreadIndices.get(0) : attrs.size();
for (int i = 0; i < firstSpreadIndex; i++) {
props.add(convertJSXAttribute((JSXPropertyAttribute) attrs.get(i)));
if (REACT_ELEMENT_PROPERTY_KEY_NAME.equals(pAttr.getPropertyAsText())) {
continue;
}
pas.add(_PropertyNameValuePair(
getNameFromPropertyAttribute(pAttr),
getValueExpressionFromPropertyAttribute(pAttr)));
}
ObjectLiteral target = _ObjLit(props.toArray(new PropertyNameValuePair[0]));

List<Expression> parameters = new ArrayList<>();
parameters.add(target);
}

for (int i = 0; i < spreadIndices.size(); i++) {
int curSpreadIdx = spreadIndices.get(i);
// Spread expression passed is used directly
parameters.add(((JSXSpreadAttribute) attrs.get(curSpreadIdx)).getExpression());
// Combine properties between spread intervals
int nextSpreadIdx = (i < spreadIndices.size() - 1) ? spreadIndices.get(i + 1)
: attrs.size();
List<JSXAttribute> propsBetweenTwoSpreads = attrs.subList(curSpreadIdx + 1, nextSpreadIdx);
if (!propsBetweenTwoSpreads.isEmpty()) {
List<PropertyAssignment> props2 = new ArrayList<>();
for (JSXAttribute attr : propsBetweenTwoSpreads) {
props2.add(convertJSXAttribute((JSXPropertyAttribute) attr));
}
parameters.add(_ObjLit(props2.toArray(new PropertyAssignment[0])));
}
if (!children.isEmpty()) {
Expression childrenValue;
if (children.size() == 1) {
childrenValue = convertJSXChild(children.get(0));
} else {
childrenValue = _ArrLit(
toList(map(children, child -> convertJSXChild(child))).toArray(new Expression[0]));
}

return _CallExpr(_PropertyAccessExpr(steFor_Object(), steFor_Object_assign()),
parameters.toArray(new Expression[0]));
// this will cause any other custom property children to be overwritten
pas.add(_PropertyNameValuePair(REACT_ELEMENT_PROPERTY_CHILDREN_NAME, childrenValue));
}

return _ObjLit(pas.toArray(new PropertyAssignment[0]));
}

private PropertyNameValuePair convertJSXAttribute(JSXPropertyAttribute attr) {
return _PropertyNameValuePair(
getNameFromPropertyAttribute(attr),
getValueExpressionFromPropertyAttribute(attr));
private Expression convertJSXChild(JSXChild child) {
if (child instanceof JSXElement) {
return convertJSXAbstractElement((JSXElement) child);
}
if (child instanceof JSXFragment) {
return convertJSXAbstractElement((JSXFragment) child);
}
if (child instanceof JSXExpression) {
return ((JSXExpression) child).getExpression();
}
return null;
}

private Expression getTagNameFromElement(JSXElement elem) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@
import java.util.Map;
import java.util.Objects;

import org.apache.log4j.Logger;
import org.eclipse.n4js.n4JS.ImportDeclaration;
import org.eclipse.n4js.n4JS.ModuleSpecifierForm;
import org.eclipse.n4js.packagejson.projectDescription.ProjectType;
import org.eclipse.n4js.tooling.react.ReactHelper;
import org.eclipse.n4js.transpiler.Transformation;
import org.eclipse.n4js.ts.types.TModule;
import org.eclipse.n4js.utils.DeclMergingUtils;
Expand All @@ -39,6 +41,7 @@
* For details, see {@link #computeModuleSpecifierForOutputCode(ImportDeclaration)}.
*/
public class ModuleSpecifierTransformation extends Transformation {
private final static Logger LOGGER = Logger.getLogger(ModuleSpecifierTransformation.class);

@Inject
private WorkspaceAccess workspaceAccess;
Expand Down Expand Up @@ -121,6 +124,17 @@ private void transformImportDecl(ImportDeclaration importDeclIM) {
private String computeModuleSpecifierForOutputCode(ImportDeclaration importDeclIM) {
TModule targetModule = getState().info.getImportedModule(importDeclIM);

if (targetModule == null) {
if (ReactHelper.REACT_JSX_RUNTIME_NAME.equals(importDeclIM.getModuleSpecifierAsText())) {
// expected to happen since this import was added by JSXTransformation
return importDeclIM.getModuleSpecifierAsText();
}
// fallback, should not happen
LOGGER.error("targetModule is null at import declaration with module specifier: "
+ importDeclIM.getModuleSpecifierAsText());
return importDeclIM.getModuleSpecifierAsText();
}

if (URIUtils.isVirtualResourceURI(targetModule.eResource().getURI())
&& !DeclMergingUtils.isModuleAugmentation(targetModule)) {
// SPECIAL CASE #1a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*/
package org.eclipse.n4js.transpiler.es.transform;

import static org.eclipse.n4js.tooling.react.ReactHelper.REACT_JSX_RUNTIME_NAME;
import static org.eclipse.xtext.xbase.lib.IterableExtensions.exists;
import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter;
import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList;
Expand Down Expand Up @@ -158,6 +159,11 @@ private boolean isUsed(ImportSpecifier importSpec) {
// add new usages of an existing import)
// -> therefore simply return false (i.e. unused)
return false;
} else if (importSpec.eContainer() instanceof ImportDeclaration
&& REACT_JSX_RUNTIME_NAME
.equals(((ImportDeclaration) importSpec.eContainer()).getModuleSpecifierAsText())) {
// special case for import that is added in JSXTransformation
return true;
} else {
// for performance reasons, we do not require the flaggedUsedInCode to be set to false if usages are removed
// -> therefore have to check for references now
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
import org.eclipse.n4js.n4JS.PropertyNameKind;
import org.eclipse.n4js.n4JS.PropertyNameOwner;
import org.eclipse.n4js.n4JS.PropertyNameValuePair;
import org.eclipse.n4js.n4JS.PropertySpread;
import org.eclipse.n4js.n4JS.RelationalExpression;
import org.eclipse.n4js.n4JS.RelationalOperator;
import org.eclipse.n4js.n4JS.ReturnStatement;
Expand Down Expand Up @@ -474,6 +475,12 @@ public static PropertyNameValuePair _PropertyNameValuePair(LiteralOrComputedProp
return result;
}

public static PropertySpread _PropertySpread(Expression value) {
PropertySpread result = N4JSFactory.eINSTANCE.createPropertySpread();
result.setExpression(value);
return result;
}

public static PropertyGetterDeclaration _PropertyGetterDecl(String name, Statement... stmnts) {
PropertyGetterDeclaration result = N4JSFactory.eINSTANCE.createPropertyGetterDeclaration();
result.setDeclaredName(_LiteralOrComputedPropertyName(name));
Expand Down
Loading

0 comments on commit 6011bcd

Please sign in to comment.