Command Line Linter for UI5 based projects.
Any support is highly appreciated!
Execute in command line:
npm install ui5plugin-linter -g
After installing the package globally the linter will be available:
ui5linter
Linter also can be used from local node_modules
:
npm install ui5plugin-linter --save-dev
npx ui5linter
If tsconfig.json
is found in the CWD and any .ts
files are found in the workspace, parser considers that it's TS project.
tsconfig.json
should be located in CWD.
For convenience purposes UI5TSParser
ignoressrc-gen
folders, because they contain transpiled JS/XML files, which can make the parser to think that source files are there.
Important! If build folder name is different, it should be added to
excludeFolderPatterns
in yourpackage.json
.
Linter, which checks if all members of parent class (which is abstract) are implemented.
Checks if interface members of an interface are implemented.
Checks if currently public member should be private. If no references for the member are found outside the current class, error will be shown.
Checks if member is used anywhere.
Checks if class name is correct.
Checks for several things:
Checks if all paths which starts with app id exists
Checks if imported module exists and is not deprecated
Checks if overriden member is not private and if parent member is not deprecated
Checks for several things:
Hint! To set the type for the variable, JSDocs can be used.
/**@type {sap.m.Table}*/
let oTable;
Hint! To make parameter optional, JSDocs can be used.
/**
* @param {string} mandatoryParam mandatory parameter
* @param {string} [optionalParam] optional parameter
*/
myMethod(mandatoryParam, optionalParam) {}
Checks if class is used
Checks if namespace is provided and if it is correct.
Checks if correct type for event variable is used in event handlers
Checks if attribute
Checks if entered value is not the same as default value
Checks if:
Checks if namespace is used
Checks if class exists
Checks if translation is used
Checks if translation is not duplicated
UI5 Linter searches for package.json
(or any other rc
file, e.g. .ui5pluginrc
) in your CWD (Current Working Directory) and locates the config there.
Supported rc
file types:
.ui5pluginrc
.ui5pluginrc.json
.ui5pluginrc.yaml
.ui5pluginrc.yml
.ui5pluginrc.js
See configuration example below.
Cache
ui5plugin-parser preloads the library metadata and stores it in cache. IflibsToLoad
were changed, it is necessary to clear cache. It is possible by adding--rmcache
flag to ui5linter:ui5linter --rmcache
{
"ui5": {
"ui5parser": {
"ui5version": "1.84.30",
"dataSource": "https://sapui5.hana.ondemand.com/",
"rejectUnauthorized": true,
"libsToLoad": ["sap.uxap", "sap.viz"],
//Handy to add additional workspace paths if e.g. library is outside of CWD
"additionalWorkspaces": ["../MyLibrary"]
},
"ui5linter": {
"severity": {
"WrongParametersLinter": "Error",
"WrongOverrideLinter": "Warning",
"WrongImportLinter": "Information",
"WrongFilePathLinter": "Hint"
},
"usage": {
"WrongParametersLinter": true,
"WrongOverrideLinter": false
},
"jsLinterExceptions": [
{
"className": "com.test.MyCustomClass",
/*method or field name*/
"memberName": "myCustomMethod",
/*all classes which extends com.test.MyCustomClass will
inherit this exception as well*/
"applyToChildren": true
}
],
/*classes to exclude from linting*/
"jsClassExceptions": ["com.test.MyCustomClass1", "com.test.MyCustomClass2"],
/*views and fragments to exclude from linting*/
"xmlClassExceptions": ["com.test.view.Master", "com.test.fragment.MyToolbar"],
/*array of i18n translation ids to be ignored by UnusedTranslationsLinter*/
"propertiesLinterExceptions": ["MyView.MyButtonText", "MY_TRANSLATION_ID"],
"componentsToInclude": ["com.test"],
/*it makes sense to use only componentsToInclude or componentsToExclude, but not both at once.
"componentsToExclude" comes in handy when you want to exclude e.g. libraries.
"componentsToInclude" comes handy when you have many different components which project depends
on, but it is necessary to lint only one*/
"componentsToExclude": ["com.custom.library"]
}
}
}
Default config is as follows:
{
"ui5": {
"ui5linter": {
"severity": {
"WrongParametersLinter": "Error",
"WrongOverrideLinter": "Error",
"WrongImportLinter": "Warning",
"WrongFilePathLinter": "Warning",
"WrongFieldMethodLinter": "Warning",
"WrongClassNameLinter": "Warning",
"UnusedTranslationsLinter": "Information",
"UnusedNamespaceLinter": "Error",
"UnusedMemberLinter": "Information",
"TagLinter": "Error",
"TagAttributeLinter": "Error",
"TagAttributeDefaultValueLinter": "Information",
"PublicMemberLinter": "Information",
"InterfaceLinter": "Error",
"AbstractClassLinter": "Error",
"UnusedClassLinter": "Error",
"WrongNamespaceLinter": "Warning",
"DuplicateTranslationLinter": "Error",
"EventTypeLinter": "Error"
},
"usage": {
"WrongParametersLinter": true,
"WrongOverrideLinter": true,
"WrongImportLinter": true,
"WrongFilePathLinter": true,
"WrongFieldMethodLinter": true,
"WrongClassNameLinter": true,
"UnusedTranslationsLinter": true,
"UnusedNamespaceLinter": true,
"UnusedMemberLinter": true,
"TagLinter": true,
"TagAttributeLinter": true,
"TagAttributeDefaultValueLinter": true,
"PublicMemberLinter": true,
"InterfaceLinter": true,
"AbstractClassLinter": true,
"UnusedClassLinter": true,
"WrongNamespaceLinter": true,
"DuplicateTranslationLinter": true,
"EventTypeLinter": true //true if UI5 version is >=1.115.1
},
"jsLinterExceptions": [
{
"className": "sap.ui.core.Element",
"memberName": "getDomRef",
"applyToChildren": true
},
{
"className": "sap.ui.model.json.JSONModel",
"memberName": "iSizeLimit",
"applyToChildren": true
},
{
"className": "sap.ui.model.Binding",
"memberName": "*"
},
{
"className": "sap.ui.model.Model",
"memberName": "*"
},
{
"className": "sap.ui.core.Element",
"memberName": "*"
},
{
"className": "sap.ui.base.ManagedObject",
"memberName": "*"
},
{
"className": "sap.ui.core.Control",
"memberName": "*"
},
{
"className": "sap.ui.xmlfragment",
"memberName": "*"
},
{
"className": "*",
"memberName": "byId"
},
{
"className": "*",
"memberName": "prototype"
},
{
"className": "*",
"memberName": "call"
},
{
"className": "*",
"memberName": "apply"
},
{
"className": "*",
"memberName": "bind"
},
{
"className": "*",
"memberName": "constructor"
},
{
"className": "*",
"memberName": "init"
},
{
"className": "*",
"memberName": "exit"
},
{
"className": "map",
"memberName": "*"
}
],
"jsClassExceptions": [],
"xmlClassExceptions": [],
"propertiesLinterExceptions": [],
"componentsToInclude": [],
"componentsToExclude": [],
"idNamingPattern": "^id{MeaningAssumption}.*{ControlName}$",
"eventNamingPattern": "^on{MeaningAssumption}{ControlName}.*?{EventName}$",
"attributesToCheck": ["content", "items", "value", "text", "number"]
}
}
}
It is possible to override properties in your package.json
. See Configuration example
In case of
jsLinterExceptions
the exceptions which will be found inpackage.json
of CWD will be added to the default exceptions, in the rest of the cases properties will be overwritten
It is possible to add config for ui5plugin-parser as well.
Check ui5plugin-parser ->
Config default values
as a reference for parser properties See Configuration example
There are multiple ways to configure linter exceptions.
To ignore class member errors, there are two ways to do it. Lets assume that there is a line, which gives incorrect amount of arguments error
this._myMethod("123", 643, true); //Method "_myMethod" has 2 (2 mandatory) param(s), but you provided 3
In order to ignore this error, there are two ways to do it:
@ui5ignore
to method declaration JSDoc/**
* @ui5ignore
*/
_myMethod(sFirstParam, iSecondParam) {}
jsLinterExceptions
in package.json
:{
"ui5": {
"ui5linter": {
"jsLinterExceptions": [
{
"className": "com.test.MyClass",
"memberName": "_myMethod",
"applyToChildren": true
}
]
}
}
}
To ignore attribute and tag errors, <-- @ui5ignore -->
can be used. It will ignore all errors of next tag and its attributes.
<!-- @ui5ignore -->
<RandomText
randomProperty="123"
/>
To ignore specific attribute, <-- @ui5ignore ${attributeName1}, ${attributeName2} -->
can be used. It will ignore all attribute errors
<!-- @ui5ignore randomProperty, secondRandomProperty -->
<Text
randomProperty="123"
secondRandomProperty="123"
text="Valid property"
/>
To ignore all id and event handler pattern errors of the next tag, <-- @ui5ignore-patterns -->
can be used.
<!-- @ui5ignore-patterns -->
<Text
id="myRandomId"
/>
To ignore specific id or event handler pattern errors of the next tag, <-- @ui5ignore-patterns ${attributeName1}, ${attributeName2} -->
can be used.
<!-- @ui5ignore-patterns id -->
<Button
id="myRandomId"
press="myRandomHandler"
/>
To ignore translation errors, # @ui5ignore
comment right above the translation can be used
# @ui5ignore
MY_TITLE = Title
To exclude whole class from linting, add class name to package.json
. In case of JS Classes, use jsClassExceptions
. In case of view and fragments, use xmlClassExceptions
{
"ui5": {
"ui5linter": {
"jsClassExceptions": ["com.test.MyClass"]
}
}
}
To exclude translation from linting in 18n.properties
file, add class name to package.json
{
"ui5": {
"ui5linter": {
"propertiesLinterExceptions": ["MY_TRANSLATION_ID"]
}
}
}
Lets assume that there is a library included in the project, and there is no need to lint it.
To exclude whole app or lib from linting, add app/lib id to package.json
. There are two preference entries that can be used: componentsToInclude
and componentsToExclude
.
{
"ui5": {
"ui5linter": {
"componentsToInclude": ["com.test.my.app"],
"componentsToExclude": ["com.test.my.lib"]
}
}
}
In order to keep the same style for naming the id
and any event handler
in views and fragments, three configuration entries are available: idNamingPattern
, eventNamingPattern
, attributesToCheck
. Every id
and event handler
generates individual RegExp
, and the value is checked against it.
Hint! To disable naming style checking, just set
idNamingPattern
or/andeventNamingPattern
as empty string inpackage.json
In order to generate individual RegExp
, there are variables available which will be applied at runtime. All variables are available in camelCase
and PascalCase
.
Example of eventNamingPattern
: ^on{MeaningAssumption}{ControlName}.*?{EventName}$
.
Variables: MeaningAssumption
, ControlName
, EventName
.
<Input
value="{ODataModel>Currency}"
change=""
>
<!-- Pattern for 'change': ^onCurrencyInput.*?Change$ -->
Variable is replaced with the name of the tag. Available for both id
and event handler
patterns.
<Text/>
<!-- ControlName => Text -->
<!-- controlName => text -->
<f:GridList/>
<!-- ControlName => GridList -->
<!-- controlName => gridList -->
Variable is replaced with the name of the event. Available for event handler
patterns only.
<Button press="onButtonPress"/>
<!-- EventName => Press -->
<!-- eventName => press -->
<f:GridList borderReached="onGridListBorderReached"/>
<!-- EventName => BorderReached -->
<!-- eventName => borderReached -->
Variable is replaced with guessed value taken from attribute. Available for both id
and event handler
patterns. This is the most tricky variable.
It is calculated as follows:
attributesToCheck
configurationattributesToCheck
array.MeaningAssumption
path
field, otherwise crop {
and }
>
, left part is model name, right part is binding path
i18n
, find the translation in i18n.properties
, transform value to PascalCase and return the value/results
from binding path
, split the result by _
, transform to PascalCase and return the valueAttribute value | MeaningAssumption value |
---|---|
{/OrderItems} | OrderItems |
{OrderId} | OrderId |
{/OrderItems/results} | OrderItems |
{ODataModel>/ORDER_ITEMS} | OrderItems |
{MyModel>/Orders} | Orders |
{i18n>APP_TITLE} | OrderApp (if text in i18n.properties is "Order App") |
Hint! >
MeaningAssumption
is pure heuristics and of course will not work for all the cases. If it works most of the time well, but there are some exceptions, it is possible to ignore specific tag attribute errors. Check View and fragment errors section.
Example.
<List
id="idOrderItemsList"
items="{ODataModel>/OrderItems}"
press="onOrderItemsListPress"
/>
<!--
ControlName => List
controlName => list
EventName => Press
eventName => press
MeaningAssumption => OrderItems
meaningAssumption => orderItems
-->
This configuration is a list of attributes, where MeaningAssumption
will be guessed from.
For instance, if attributesToCheck
is set to "attributesToCheck": ["items"]
, the value will be guessed only from items
attribute, e.g. in sap.m.List
:
<List
id="idOrdersList"
items="{/Orders}"
/>
It is possible to format fragments and views using CMD.
ui5linter --format
It is possible to set the path pattern of the files which should be formatted.
Default value: **/*.{fragment,view}.xml
ui5linter --format --path=**/*.{fragment,view}.xml
It is possible to pass a parameter if closing tags (">", "/>") should be located on newline
ui5linter --format --tagEndNewline
It is possible to pass a parameter if a space should be added before a self-closing tag ("/>")
ui5linter --format --tagSpaceBeforeSelfClose
It is possible to pass a parameter which will determine indentation
ui5linter --format --indentation=space --spaces=4
ui5linter --format --indentation=tab
The technical interface of possible entries:
interface IUI5PackageConfigEntry {
ui5?: IUI5LinterEntry;
}
interface IUI5LinterEntry {
ui5linter?: IUI5LinterEntryFields;
}
interface IUI5LinterEntryFields {
severity?: {
[key in JSLinters | XMLLinters | PropertiesLinters]: Severity;
};
usage?: {
[key in JSLinters | XMLLinters | PropertiesLinters]: boolean;
};
jsLinterExceptions?: JSLinterException[];
jsClassExceptions?: string[];
xmlClassExceptions?: string[];
propertiesLinterExceptions?: string[];
componentsToInclude?: string[];
componentsToExclude?: string[];
idNamingPattern?: string;
eventNamingPattern?: string;
attributesToCheck?: string[];
}
Enumerations:
enum PropertiesLinters {
UnusedTranslationsLinter = "UnusedTranslationsLinter",
DuplicateTranslationLinter = "DuplicateTranslationLinter"
}
enum XMLLinters {
TagAttributeLinter = "TagAttributeLinter",
TagAttributeDefaultValueLinter = "TagAttributeDefaultValueLinter",
TagLinter = "TagLinter",
UnusedNamespaceLinter = "UnusedNamespaceLinter",
WrongFilePathLinter = "WrongFilePathLinter"
}
enum JSLinters {
AbstractClassLinter = "AbstractClassLinter",
InterfaceLinter = "InterfaceLinter",
PublicMemberLinter = "PublicMemberLinter",
UnusedMemberLinter = "UnusedMemberLinter",
WrongClassNameLinter = "WrongClassNameLinter",
WrongFieldMethodLinter = "WrongFieldMethodLinter",
WrongFilePathLinter = "WrongFilePathLinter",
WrongImportLinter = "WrongImportLinter",
WrongOverrideLinter = "WrongOverrideLinter",
WrongParametersLinter = "WrongParametersLinter",
UnusedClassLinter = "UnusedClassLinter",
WrongNamespaceLinter = "WrongNamespaceLinter",
EventTypeLinter = "EventTypeLinter"
}
enum Severity {
Warning = "Warning",
Error = "Error",
Information = "Information",
Hint = "Hint"
}