Skip to content
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

[INTERNAL] Add documentation to folders #51

Open
wants to merge 7 commits into
base: master
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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ A list of currently available migration tasks can be found [here](./docs/guide/t
### Formatting options
A list of options to configure the formatting of migration output can be found [here](./docs/guide/print.md)

### Create a new code replacement
A guide with sample for creating a new code replacement can be found [here](./docs/guide/newReplacement.md)

## Contributing
Please check our [Contribution Guidelines](https://github.com/SAP/ui5-migration/blob/master/CONTRIBUTING.md). Your input and support is welcome!

Expand Down
344 changes: 344 additions & 0 deletions docs/guide/newReplacement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,344 @@
# Introducing a new replacement

With the requirement of performing a new code modification, the first question is, which task should
be used to perform the code modification. A brief look at [Tasks](./tasks.md) can help to decide what
kind of task the code modification matches.

## Find the right task

Ideally a new replacement should be done by enhancing the configuration of a task.

| Feasible | Name | Config (./defaultConfig) | Description |
|:--------:|:---------------------------:|:----------------------------------:|--------------------------------------------------------------|
| ✅ | `fix-jquery-plugin-imports` | addMissingDependencies.config.json | Complex replacements, very dynamic |
| ❌ | `add-renderer-dependencies` | addRenderers.config.json | Cannot be enhanced via configuration |
| ❌ | `apply-amd-syntax` | AmdCleaner.config.json | Cannot be enhanced via configuration |
| ❌ | `fix-type-dependencies` | fixTypeDependency.config.json | Cannot be enhanced via configuration |
| ✅ | `replace-globals` | replaceGlobals.config.json | Simple replacements of existing api calls with new api calls |


### Simple replacements via replaceGlobals
For simple modification use replaceGlobals, e.g.
before:
```js
sap.ui.define(["jquery.sap.script"], function(jQuery) {
"use strict";

jQuery.each(window.navigator.languages, function (i, value) {
console.log(i + ":" + value);
});
});
```

after code modification:
```js
sap.ui.define(["sap/base/util/each"], function(each) {
tobiasso85 marked this conversation as resolved.
Show resolved Hide resolved
"use strict";

each(window.navigator.languages, function (i, value) {
console.log(i + ":" + value);
});
});
```

The config for this replacement is:
```json
{
"jquery.sap.script": {
"jQuery.sap.each": {
"newModulePath": "sap/base/util/each"
}
}
}
```

**Note**: the variable name "each" is automatically derived from the `modulePath`, alternatively parameter
`newVariableName` could be provided, if the variable name should differ.

You also realize that you cannot configure much.
You can only influence the
* import via the top level string, e.g. `"jquery.sap.script"`
* the call to replace is found via the second level string, e.g. `"jQuery.sap.each"`
* the replacement itself is found via the third level `"newModulePath": "sap/base/util/each"`


### Complex/Advanced replacements via addMissingDependencies
If your code modification is more advanced use the addMissingDependencies task approach, although
the name suggests something different, these code modifications are highly customizable.
You can configure for a code modification:
* **what** code should be replaced (via a custom `Finder`, which supports a find function and has the currently processed AST Node as argument)
* **how** it should be replaced (via a custom `Replacer`, which supports a replace function and has the found AST Node at hand)
* **how2** the import should look like (via a custom `Extender`, which supports an extend function and has the complete sap.ui.define AST Node)

Since all of them are custom and can be freely configured it brings the most flexibility.
Examples:
* **What**: Alls calls of a "doCalculateIt" function should be replaced which have as argument a positive integer number
* **How**: if the decimal number is even it should be replaced with module "a" and function "even", otherwise with function "odd"
* **How2**: there should be an import added for module "a"

To help out with understanding how the AST is structure, the following page helps:
[Esprima AST](https://esprima.org/demo/parse.html)

#### Example


##### Config

All pieces are glued together in the config `addMissingDependencies.config.json`.
The config is consists of 2 parts, Modules and Definitions

##### Modules
The `modules` section represents the code modifications which should be performed.
Top level it contains a custom grouping id, which can be freely chosen, e.g. `jQuery Function Extensions`
The second level contains a unique id for the replacement, it can also be freely chosen,
unless a `Finder` uses it for determining a name, e.g. with id `*.cursorPos` the finder
`FunctionExtensionFinder.ts` will look for functions named `cursorPos`.
Keep in mind that the second level id (configName) is passed, e.g. to the `Finder` and used there, so it could play a role
Inside this object there are 3 mandatory keys: `finder`, `extender` and `replacer`, their values are an
alias which can be defined and the respective top level sections `finders`, `extenders` and `replacers`

All other key-values can be accessed inside the finder/extender/replacer code.


##### Definitions
The definition's area contains sections for `finders`, `extenders` and `replacers`
These consist have a mapping of alias to file name, the alias can then be used in the `modules` sections code replacements


Sections `comments` and `excludes` we ignore for now.

```json
{
"modules": {
"my grouping": {
"my replacement": {
"newModulePath": "my/numbermodule/a",
"newVariableName": "a",
"replacer": "MyCustomReplacer",
"finder": "MyCustomFinder",
"extender": "MyCustomExtender"
}
}
},
"finders": {
"MyCustomFinder": "tasks/helpers/finders/MyCustomFinder.js"
},
"extenders": {
"MyCustomExtender": "tasks/helpers/extenders/MyCustomExtender.js"
},
"replacers": {
"MyCustomReplacer": "tasks/helpers/replacers/MyCustomReplacer.js"
},
"comments": {
"unhandledReplacementComment": "TODO unhandled replacement"
},
"excludes": []
}
```



##### Finder
* **What**: All calls of a "doCalculateIt" function should be replaced which have as argument a positive integer number
```ts
import {Syntax} from "esprima";
import * as ESTree from "estree";

import {EMPTY_FINDER_RESULT, Finder, FinderResult} from "../../../dependencies";
import {SapUiDefineCall} from "../../../util/SapUiDefineCall";
class MyCustomFinder implements Finder {
/**
* Finds expression that is a function call named `doCalculateIt` and has a positive integer number as argument
*/
find(
node: ESTree.Node,
config: {},
sConfigName: string,
defineCall: SapUiDefineCall
): FinderResult {

// find calls e.g. to `myObject.doCalculateIt(7)`
if (
// is a call with positive integer number as argument
node.type === Syntax.CallExpression && node.arguments.length === 1
&& node.arguments[0].type === Syntax.Literal
&& typeof node.arguments[0].value === "number"
&& node.arguments[0].value >= 0

// function name is "doCalculateIt" on an unknown variable
&& node.callee.type === Syntax.MemberExpression
&& node.callee.property.type === Syntax.Identifier
&& node.callee.property.name === "doCalculateIt"
) {
return {configName: sConfigName};
} else {
return EMPTY_FINDER_RESULT;
}
}
}

module.exports = new MyCustomFinder();
```

##### Replacer
* **How**: if the decimal number is even it should be replaced with module "a" and function "even", otherwise with function "odd"

```ts
import {Syntax} from "esprima";
import * as recast from "recast";
import {ASTReplaceable, NodePath} from "ui5-migration";

const builders = recast.types.builders;
class MyCustomReplacer implements ASTReplaceable {
/**
* Finds expression that is a function call named `doCalculateIt` and has a positive integer number as argument
*/
replace(
node: NodePath, // AST Node wrapper which can be used to navigate to the parent
name: string, // variable name
fnName: string,
oldModuleCall: string,
config: {newVariableName: string} //config object
): void {

// find calls e.g. to `myObject.doCalculateIt(7)`
if (
// is a call with positive integer number as argument
node.value.type === Syntax.CallExpression
&& node.value.arguments[0].type === Syntax.Literal
tobiasso85 marked this conversation as resolved.
Show resolved Hide resolved
&& typeof node.value.arguments[0].value === "number"
&& node.value.callee.type === Syntax.MemberExpression
) {
node.value.callee.object = builders.identifier(name);
// even
if (node.value.arguments[0].value % 2 === 0) {
node.value.callee.property = builders.identifier("even");
// odd
} else {
node.value.callee.property = builders.identifier("odd");
}
}
}
}

module.exports = new MyCustomReplacer();
```

#### Extender
* **How2**: there should be an import added for module "a"
```ts
import {Extender} from "../../../dependencies";
import {SapUiDefineCall} from "../../../util/SapUiDefineCall";

/**
* Adds an import to the define statement
*/
class MyCustomExtender implements Extender {
extend(
defineCall: SapUiDefineCall,
config: {
newModulePath: string;
newVariableName: string;
}
): boolean {
return defineCall.addDependency(
config.newModulePath,
config.newVariableName
);
}
}

module.exports = new MyCustomExtender();
```

##### How to test it
Create 3 files inside the folder test/addMissingDependencies
* source file, e.g. mytestfile.js
* expected file, e.g. mytestfile.expected.js
* config file, e.g. mytestfile.config.json

Reference the test file inside `test/addMissingDependencies.ts`

```ts
it("new test for mytest", done => {
const subDir = rootDir + "replaceJQuery2/";
const expectedContent = fs.readFileSync(
subDir + "mytestfile.expected.js",
"utf8"
);
const config = JSON.parse(
fs.readFileSync(subDir + "mytestfile.config.json", "utf8")
);
const module = new CustomFileInfo(subDir + "mytestfile.js");
analyseMigrateAndTest(
module,
true,
expectedContent,
config,
done,
[]
);
});
```

and the build it and run the unit test

Note: use `npm run build` to compile the typescript sources

Run `npm run unit` to run the unit tests

Use `it.only(...)` in test files, such that only a [single test](https://mochajs.org/#exclusive-tests) is run.

## Different replacements depending on version
If a code modification depends on the UI5 version being used the following approach should be taken:

The name of the config (second level) should have an `@version` attached and the object should have the property
`version` in the [semver](https://semver.org/) format.

Note: the version property is used for the version comparison, without this property it is treated as `latest`.
The name of the config (second level) must have the `@` with another string to make sure that there are variants for the replacement call.

### example in `addMissingDependencies.config.json`

```json
{
"jQuery Function Extensions": {
"*[email protected]": {
"newModulePath": "sap/ui/dom/jquery/control",
"replacer": "AddComment",
"commentText": " jQuery Plugin \"control\"",
"finder": "JQueryFunctionExtensionFinderWithDependencyCheck",
"extender": "AddUnusedImportWithComment",
"version": "^1.58.0"
},
"*[email protected]": {
"newModulePath": "sap/ui/core/Element",
"newVariableName": "UI5Element",
"replacer": "UI5ElementClosestTo",
"finder": "JQueryControlCallFinder",
"extender": "AddImport",
"version": "^1.106.0"
}
}
}
```

### example in `replaceGlobals.config.json`

```json
{
"jquery.sap.encoder": {
"[email protected]": {
"newModulePath": "sap/base/security/URLWhitelist",
"functionName": "validate",
"version": "^1.58.0"
},
"[email protected]": {
"newModulePath": "sap/base/security/URLListValidator",
"functionName": "clear",
"version": "^1.85.0"
}
}
}
```

14 changes: 14 additions & 0 deletions src/reporter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# About

This package contains the reporting mechanism.
The reporting mechanism is used to:
* collect Information about migration entries (findings)
* create a report at the end of a migration (analysis), e.g. as console dump

# Contents

It contains Reporter classes implementing the `Reporter` interface.

## Naming convention

All files in here should end with `Reporter` since they implement the Reporter interface
Loading