Skip to content

IBX-8365: Use babel to automatic refactor frontend code #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"extends": "eslint-config-ibexa/eslint"
"extends": "eslint-config-ibexa/eslint"
}
23 changes: 23 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const { getModifyMethod } = require('./js/helpers.js');

module.exports = function (api) {
const modifyPlugins = getModifyMethod('plugins');
const ibexaPlugins = [
'./js/ibexa-rename-ez-global.js',
'./js/ibexa-rename-variables.js',
'./js/ibexa-rename-string-values.js',
'./js/ibexa-rename-trans-id.js',
'./js/ibexa-rename-in-translations.js',
];
const finalPlugins = modifyPlugins(ibexaPlugins);

api.cache(true);

const presets = [];
const plugins = ['@babel/plugin-syntax-jsx', ...finalPlugins];

return {
presets,
plugins,
};
};
199 changes: 199 additions & 0 deletions js/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# Get ready to run
```
yarn --cwd ./vendor/ibexa/rector/js install
```
`--cwd` argument should point to directory where JS transform module is installed (for example `./vendor/ibexa/rector/js`). This installs node_modules inside vendor bundle.

# How to run
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doc-team proof read?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not yet, as stated in PR's TODO it has to be adjusted a little, but I'd rather do it after CR of rest in case of you find some glaring error. :)

```
yarn --cwd ./vendor/ibexa/rector/js transform
```
`--cwd` argument should point to directory where JS transform module is installed (for example `./vendor/ibexa/rector/js`).

## Configuration file
If you need to modify default config, plugins or configuration for rules, use `rector.config.js` from main directory of bundle.
```
module.exports = {
config: {
paths: [{
input: 'src/bundle/Resources/public',
output: 'src/bundle/Resources/public',
}],
prettierConfigPath: './prettier.js',
}
plugins: (plugins) => {
// modify enabled plugins

return plugins;
},
pluginsConfig: (config) => {
// modify plugins config

return config;
}
};
```

### config

#### paths
Array of objects with input and output directories for transformed files. By default it's relative to main bundle root. While transforming, structure of directories is maintained.

#### prettierConfigPath
Optional. At the end of transform there's mandatory prettier execution, which by default is taken from package https://github.com/ibexa/eslint-config-ibexa/blob/main/prettier.js

### plugins
Allows to modify enabled plugins (more about plugin below).

### pluginsConfig
Allows to modify config for plugins.
Example config:
```
{
"ibexa-rename-string-values": {
"ez-form-error": "ibexa-form-error",
"ez-selection-settings": {
"to": "ibexa-selection-settings",
"exactMatch": true
},
"(^|\\s)\\.ez-": {
"to": ".ibexa-",
"regexp": true
},
"ibexa-field-edit--ez([A-Za-z0-9]+)": {
"to": "ibexa-field-edit--ibexa$1",
"regexp": true
}
}
}
```
Plugin config is kept as object with key being plugin name (`ibexa-rename-string-values` in example).

Property key inside this object is string that is supposed to be changed, can be either standard string or regex (`(^|\\s)\\.ez-`, `ezform-error"` in example)

#### Shorthand expression

`"ez-form-error": "ibexa-form-error"` - change all `ez-form-error` occurences to `ibexa-form-error`

#### Full object config properties

`"to": "ibexa-selection-settings"` - what string should be replaced with

`"regexp": true/false` - should config use regexp to match original value

`"exactMatch": true` - should match only full values, using example config, this won't change `ez-selection-settings__field` as `ez-selection-settings` is not exact match.

#### Special "shared" property
Except named plugins config, there is also possibility to create config for all plugins - its rules are later overwritten by specific plugin config if there is intersection in rules names.
```
"shared": {
"ez": {
"to": "ibexa",
"exactMatch": true,
}
}
```

## Default plugins
### Rename eZ global variables
This plugin changes all `eZ` variables to `ibexa`.

**Plugin name in config:** `./ibexa-rename-ez-global.js`

**Example config:** none

### Rename variables
This plugin allows to change any variable to any other value.

**Plugin name in config:** `./ibexa-rename-variables.js`

**Example config:**
```
{
"^Ez(.*?)Validator$": {
"to": "Ibexa$1Validator",
"regexp": true
},
"^EZ_": {
"to": "IBEXA_",
"regexp": true
}
}
```

**Example output:**

`class EzBooleanValidator extends eZ.BaseFieldValidator` => `class IbexaBooleanValidator extends ibexa.BaseFieldValidator`

`const EZ_INPUT_SELECTOR = 'ezselection-settings__input';` => `const IBEXA_INPUT_SELECTOR = 'ezselection-settings__input';`

### Rename string values
This plugin allows to change any string value - except translations. Can be used to transform selectors etc.

**Plugin name in config:** `./ibexa-rename-string-values.js`

**Example config:**
```
{
"(^|\\s)\\.ez-": {
"to": ".ibexa-",
"regexp": true
},
"ibexa-field-edit--ez([A-Za-z0-9]+)": {
"to": "ibexa-field-edit--ibexa$1",
"regexp": true
},
"ezselection-settings": "ibexaselection-settings"
}
```

**Example output:**

`const SELECTOR_FIELD = '.ez-field-edit--ezboolean';` => `const SELECTOR_FIELD = ".ibexa-field-edit--ezboolean"`

`const SELECTOR_FIELD = '.ibexa-field-edit--ezboolean';` => `const SELECTOR_FIELD = '.ibexa-field-edit--ibexaboolean';`

### Rename translation IDs
This plugin allows to change translation ids. Remember to extract translations afterwards!

**Plugin name in config:** `./ibexa-rename-trans-id.js`

**Example config:**
```
{
"^ez": {
"to": "ibexa",
"regexp": true
}
}
```

**Example output:**

`'ez_boolean.limitation.pick.ez_error'` => `'ibexa_boolean.limitation.pick.ez_error'`

### Rename translation strings
This plugin allows to change translations. Remember to extract translations afterwards!

**Plugin name in config:** `./ibexa-rename-in-translations.js`

**Example config:**
```
{
"to": "ibexa-not-$1--show-modal",
"regexp": true,
"selectors-only": true
}
```

**selectors-only config:**

If this property is set to `true`, this plugin changes only strings inside html tags (like classes and other html parameters). Set to `false` or remove property to change also normal strings as well.

**Example output with selectors-only=true:**

`/*@Desc("<p class='ez-not-error--show-modal'>Show message</p> for ez-not-error--show-modal")*/` => `/*@Desc("<p class='ibexa-not-error--show-modal'>Show message</p> for ez-not-error--show-modal")*/`

**Example output with selectors-only=false:**

`/*@Desc("<p class='ez-not-error--show-modal'>Show message</p> for ez-not-error--show-modal")*/` => `/*@Desc("<p class='ibexa-not-error--show-modal'>Show message</p> for ibexa-not-error--show-modal")*/`
116 changes: 116 additions & 0 deletions js/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
const fs = require('fs');
const path = require('path');

const CONFIG_FILENAME = 'rector.config.js';

let savedMainConfig = null;

const getMainConfig = () => {
if (savedMainConfig) {
return savedMainConfig;
}

const { INIT_CWD } = process.env;
const fallbackConfigPath = path.resolve('js', CONFIG_FILENAME);
const customConfigPath = path.resolve(INIT_CWD, CONFIG_FILENAME);

if (fs.existsSync(customConfigPath)) {
savedMainConfig = require(customConfigPath);
} else if (fs.existsSync(fallbackConfigPath)) {
savedMainConfig = require(fallbackConfigPath);
} else {
throw new Error(`Config file not found: ${customConfigPath} or ${fallbackConfigPath}`);
}

return savedMainConfig;
};

const getPrettierConfigFile = (prettierConfigPath) => {
if (fs.existsSync(prettierConfigPath)) {
return prettierConfigPath;
}

return require.resolve('eslint-config-ibexa/prettier');
};
const getAbsolutePath = (pathToDir) => {
if (path.isAbsolute(pathToDir)) {
return pathToDir;
}

return path.join(process.env.INIT_CWD, pathToDir);
};

const getModifyMethod = (method) => {
const mainConfig = getMainConfig();

return mainConfig[method] ?? ((config) => config);
};

const getRulesConfig = (name) => {
const modifyConfig = getModifyMethod('config');
const rulesConfigPath = path.join(__dirname, 'rules.config.json');
const rawData = fs.readFileSync(rulesConfigPath);
const parsedData = JSON.parse(rawData);
const modifiedData = modifyConfig(parsedData);
const sharedConfig = modifiedData.shared ?? {};
const namedConfig = modifiedData[name] ?? {};

return {
...sharedConfig,
...namedConfig,
};
};

const shouldReplace = (original, oldValue, newValueConfig) => {
if (newValueConfig.exactMatch) {
return original === oldValue;
} else if (newValueConfig.regexp) {
return !!original.match(oldValue);
}

return true;
};

const getValues = (oldValue, newValueConfig) => {
if (newValueConfig.regexp) {
return {
oldValue: new RegExp(oldValue, 'g'),
newValue: newValueConfig.to,
};
}

return {
oldValue,
newValue: newValueConfig.to ?? newValueConfig,
};
};

const traverse = (moduleConfig, originalValue, replaceData) => {
Object.entries(moduleConfig).forEach(([oldValueRaw, newValueConfig]) => {
if (shouldReplace(originalValue, oldValueRaw, newValueConfig)) {
const { oldValue, newValue } = getValues(oldValueRaw, newValueConfig);

replaceData(oldValue, newValue, newValueConfig);
}
});
};

const isFunctionArgument = ({ parentPath }, functionName) => {
if (!parentPath?.isCallExpression()) {
return false;
}

return parentPath.node.callee.property?.name === functionName;
};

module.exports = {
getMainConfig,
getPrettierConfigFile,
getAbsolutePath,
getModifyMethod,
getRulesConfig,
shouldReplace,
getValues,
traverse,
isFunctionArgument,
};
16 changes: 16 additions & 0 deletions js/ibexa-rename-ez-global.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module.exports = function ({ types }) {
return {
visitor: {
Identifier(path) {
if (path.node.name === 'eZ') {
path.node.name = types.toIdentifier('ibexa');
}
},
JSXIdentifier(path) {
if (path.node.name === 'eZ') {
path.node.name = types.toIdentifier('ibexa');
}
},
},
};
};
34 changes: 34 additions & 0 deletions js/ibexa-rename-in-translations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const { getRulesConfig, traverse } = require('./helpers.js');

const moduleConfig = getRulesConfig('ibexa-rename-in-translations');

module.exports = function () {
return {
visitor: {
Identifier(path) {
if (path.node.name !== 'trans') {
return;
}
const parentCallExpresion = path.findParent((parentPath) => parentPath.isCallExpression());
const translationComment = parentCallExpresion?.node.arguments[0]?.leadingComments[0];

if (translationComment) {
traverse(moduleConfig, translationComment.value, (oldValue, newValue, config) => {
if (config['selectors-only']) {
const regexp = new RegExp(`<[a-zA-Z0-9-_]*? .*?=(?:[\\"\\'])(.*?)\\1.*?>`, 'g');
const matches = translationComment.value.match(regexp);

matches?.forEach((match) => {
const valueToReplace = match.replaceAll(oldValue, newValue);

translationComment.value = translationComment.value.replaceAll(match, valueToReplace);
});
} else {
translationComment.value = translationComment.value.replaceAll(oldValue, newValue);
}
});
}
},
},
};
};
Loading