MOBILE-3320 lint: Update linting rules

main
Noel De Martin 2020-10-14 17:20:15 +02:00
parent 58e7a78ee7
commit 43946fb61e
6 changed files with 201 additions and 121 deletions

View File

@ -15,6 +15,7 @@ module.exports = {
'prettier/@typescript-eslint',
'plugin:jest/recommended',
'plugin:@angular-eslint/recommended',
'plugin:promise/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: {
@ -22,11 +23,12 @@ module.exports = {
sourceType: 'module',
},
plugins: [
'eslint-plugin-prefer-arrow',
'eslint-plugin-jsdoc',
'@typescript-eslint',
'header',
'jest',
'jsdoc',
'prefer-arrow',
'promise',
],
rules: {
'@angular-eslint/component-class-suffix': ['error', { suffixes: ['Component', 'Page'] }],
@ -56,7 +58,29 @@ module.exports = {
accessibility: 'no-public',
},
],
'@typescript-eslint/indent': 'off',
'@typescript-eslint/explicit-module-boundary-types': [
'error',
{
allowArgumentsExplicitlyTypedAsAny: true,
},
],
'@typescript-eslint/indent': [
'error',
4,
{
SwitchCase: 1,
ignoredNodes: [
'ClassProperty *',
],
},
],
'@typescript-eslint/lines-between-class-members': [
'error',
'always',
{
exceptAfterSingleLine: true,
},
],
'@typescript-eslint/member-delimiter-style': [
'error',
{
@ -104,18 +128,6 @@ module.exports = {
'always',
],
'@typescript-eslint/type-annotation-spacing': 'error',
'@typescript-eslint/typedef': [
'error',
{
arrayDestructuring: false,
arrowParameter: false,
memberVariableDeclaration: true,
objectDestructuring: false,
parameter: true,
propertyDeclaration: true,
variableDeclaration: false,
},
],
'@typescript-eslint/unified-signatures': 'error',
'header/header': [
2,
@ -137,12 +149,20 @@ module.exports = {
],
1,
],
'promise/catch-or-return': [
'warn',
{
allowFinally: true,
},
],
'arrow-body-style': ['error', 'as-needed'],
'array-bracket-spacing': ['error', 'never'],
'comma-dangle': ['error', 'always-multiline'],
'constructor-super': 'error',
'curly': 'error',
'eol-last': 'error',
'function-call-argument-newline': ['error', 'consistent'],
'function-paren-newline': ['error', 'multiline-arguments'],
'id-blacklist': [
'error',
'any',

6
package-lock.json generated
View File

@ -7826,6 +7826,12 @@
"integrity": "sha512-C8YMhL+r8RMeMdYAw/rQtE6xNdMulj+zGWud/qIGnlmomiPRaLDGLMeskZ3alN6uMBojmooRimtdrXebLN4svQ==",
"dev": true
},
"eslint-plugin-promise": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz",
"integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==",
"dev": true
},
"eslint-scope": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",

View File

@ -130,6 +130,7 @@
"eslint-plugin-jest": "^24.1.0",
"eslint-plugin-jsdoc": "^30.6.3",
"eslint-plugin-prefer-arrow": "^1.2.2",
"eslint-plugin-promise": "^4.2.1",
"faker": "^5.1.0",
"jest": "^26.5.0",
"jest-preset-angular": "^8.3.1",

View File

@ -51,25 +51,9 @@ export class CoreIframeUtilsProvider {
constructor() {
this.logger = CoreLogger.getInstance('CoreUtilsProvider');
const win = <WKUserScriptWindow> window;
if (CoreApp.instance.isIOS() && win.WKUserScript) {
Platform.instance.ready().then(() => {
// Inject code to the iframes because we cannot access the online ones.
const wwwPath = CoreFile.instance.getWWWAbsolutePath();
const linksPath = CoreTextUtils.instance.concatenatePaths(wwwPath, 'assets/js/iframe-treat-links.js');
const recaptchaPath = CoreTextUtils.instance.concatenatePaths(wwwPath, 'assets/js/iframe-recaptcha.js');
win.WKUserScript.addScript({ id: 'CoreIframeUtilsLinksScript', file: linksPath });
win.WKUserScript.addScript({
id: 'CoreIframeUtilsRecaptchaScript',
file: recaptchaPath,
injectionTime: WKUserScriptInjectionTime.END,
});
// Handle post messages received by iframes.
window.addEventListener('message', this.handleIframeMessage.bind(this));
});
if (CoreApp.instance.isIOS() && 'WKUserScript' in window) {
// eslint-disable-next-line promise/catch-or-return
Platform.instance.ready().then(() => this.injectiOSScripts(window));
}
}
@ -214,11 +198,15 @@ export class CoreIframeUtilsProvider {
* @param contentDocument The document of the element contents.
* @param navCtrl NavController to use if a link can be opened in the app.
*/
redefineWindowOpen(element: CoreFrameElement, contentWindow: Window, contentDocument: Document,
navCtrl?: NavController): void {
redefineWindowOpen(
element: CoreFrameElement,
contentWindow: Window,
contentDocument: Document,
navCtrl?: NavController,
): void {
if (contentWindow) {
// Intercept window.open.
contentWindow.open = (url: string, name: string): Window => {
contentWindow.open = (url: string, name: string) => {
this.windowOpen(url, name, element, navCtrl);
return null;
@ -387,8 +375,11 @@ export class CoreIframeUtilsProvider {
* @param event Click event.
* @return Promise resolved when done.
*/
protected async linkClicked(link: {href: string; target?: string}, element?: HTMLFrameElement | HTMLObjectElement,
event?: Event): Promise<void> {
protected async linkClicked(
link: {href: string; target?: string},
element?: HTMLFrameElement | HTMLObjectElement,
event?: Event,
): Promise<void> {
if (event && event.defaultPrevented) {
// Event already prevented by some other code.
return;
@ -454,6 +445,27 @@ export class CoreIframeUtilsProvider {
}
}
/**
* Inject code to the iframes because we cannot access the online ones.
*
* @param userScriptWindow Window.
*/
private injectiOSScripts(userScriptWindow: WKUserScriptWindow) {
const wwwPath = CoreFile.instance.getWWWAbsolutePath();
const linksPath = CoreTextUtils.instance.concatenatePaths(wwwPath, 'assets/js/iframe-treat-links.js');
const recaptchaPath = CoreTextUtils.instance.concatenatePaths(wwwPath, 'assets/js/iframe-recaptcha.js');
userScriptWindow.WKUserScript.addScript({ id: 'CoreIframeUtilsLinksScript', file: linksPath });
userScriptWindow.WKUserScript.addScript({
id: 'CoreIframeUtilsRecaptchaScript',
file: recaptchaPath,
injectionTime: WKUserScriptInjectionTime.END,
});
// Handle post messages received by iframes.
window.addEventListener('message', this.handleIframeMessage.bind(this));
}
}
export class CoreIframeUtils extends makeSingleton(CoreIframeUtilsProvider) {}

View File

@ -450,8 +450,17 @@ export class CoreTextUtilsProvider {
* @param courseId Course ID the text belongs to. It can be used to improve performance with filters.
* @deprecated since 3.8.3. Please use viewText instead.
*/
expandText(title: string, text: string, component?: string, componentId?: string | number, files?: CoreWSExternalFile[],
filter?: boolean, contextLevel?: string, instanceId?: number, courseId?: number): void {
expandText(
title: string,
text: string,
component?: string,
componentId?: string | number,
files?: CoreWSExternalFile[],
filter?: boolean,
contextLevel?: string,
instanceId?: number,
courseId?: number,
): void {
return this.viewText(title, text, {
component,
componentId,

View File

@ -48,12 +48,8 @@ export class CoreUtilsProvider {
constructor(protected zone: NgZone) {
this.logger = CoreLogger.getInstance('CoreUtilsProvider');
Platform.instance.ready().then(() => {
if (window.cordova && window.cordova.InAppBrowser) {
// Override the default window.open with the InAppBrowser one.
window.open = window.cordova.InAppBrowser.open;
}
});
// eslint-disable-next-line promise/catch-or-return
Platform.instance.ready().then(() => this.overrideWindowOpen());
}
/**
@ -76,7 +72,7 @@ export class CoreUtilsProvider {
if (!this.isWebServiceError(error)) {
// Local error. Add an extra warning.
errorMessage += '<br><br>' + Translate.instance.instant('core.errorsomedatanotdownloaded');
errorMessage += '<br><br>' + Translate.instance.instant('core.errorsomedatanotdownloaded');
}
return errorMessage;
@ -88,35 +84,25 @@ export class CoreUtilsProvider {
* @param promises Promises.
* @return Promise resolved if all promises are resolved and rejected if at least 1 promise fails.
*/
allPromises(promises: Promise<unknown>[]): Promise<void> {
async allPromises(promises: Promise<unknown>[]): Promise<void> {
if (!promises || !promises.length) {
return Promise.resolve();
}
return new Promise((resolve, reject): void => {
const total = promises.length;
let count = 0;
let hasFailed = false;
let error;
const getPromiseError = async (promise): Promise<Error | void> => {
try {
await promise;
} catch (error) {
return error;
}
};
promises.forEach((promise) => {
promise.catch((err) => {
hasFailed = true;
error = err;
}).finally(() => {
count++;
const errors = await Promise.all(promises.map(getPromiseError));
const error = errors.find(error => !!error);
if (count === total) {
// All promises have finished, reject/resolve.
if (hasFailed) {
reject(error);
} else {
resolve();
}
}
});
});
});
if (error) {
throw error;
}
}
/**
@ -151,9 +137,13 @@ export class CoreUtilsProvider {
* @param undefinedIsNull True if undefined is equal to null. Defaults to true.
* @return Whether both items are equal.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
basicLeftCompare(itemA: any, itemB: any, maxLevels: number = 0,
level: number = 0, undefinedIsNull: boolean = true): boolean {
basicLeftCompare(
itemA: any, // eslint-disable-line @typescript-eslint/no-explicit-any
itemB: any, // eslint-disable-line @typescript-eslint/no-explicit-any
maxLevels: number = 0,
level: number = 0,
undefinedIsNull: boolean = true,
): boolean {
if (typeof itemA == 'function' || typeof itemB == 'function') {
return true; // Don't compare functions.
} else if (typeof itemA == 'object' && typeof itemB == 'object') {
@ -467,19 +457,24 @@ export class CoreUtilsProvider {
* @param ...args All the params sent after checkAll will be passed to isEnabledFn.
* @return Promise resolved with the list of enabled sites.
*/
filterEnabledSites<P extends []>(siteIds: string[], isEnabledFn: (siteId, ...args: P) => boolean | Promise<boolean>,
checkAll?: boolean, ...args: P): Promise<string[]> {
filterEnabledSites<P extends unknown[]>(
siteIds: string[],
isEnabledFn: (siteId, ...args: P) => boolean | Promise<boolean>,
checkAll?: boolean,
...args: P
): Promise<string[]> {
const promises = [];
const enabledSites = [];
for (const i in siteIds) {
const siteId = siteIds[i];
const pushIfEnabled = enabled => enabled && enabledSites.push(siteId);
if (checkAll || !promises.length) {
promises.push(Promise.resolve(isEnabledFn.apply(isEnabledFn, [siteId].concat(args))).then((enabled) => {
if (enabled) {
enabledSites.push(siteId);
}
}));
promises.push(
Promise
.resolve(isEnabledFn(siteId, ...args))
.then(pushIfEnabled),
);
}
}
@ -527,8 +522,13 @@ export class CoreUtilsProvider {
* @param maxDepth Max Depth to convert to tree. Children found will be in the last level of depth.
* @return Array with the formatted tree, children will be on each node under children field.
*/
formatTree<T>(list: T[], parentFieldName: string = 'parent', idFieldName: string = 'id', rootParentId: number = 0,
maxDepth: number = 5): TreeNode<T>[] {
formatTree<T>(
list: T[],
parentFieldName: string = 'parent',
idFieldName: string = 'id',
rootParentId: number = 0,
maxDepth: number = 5,
): TreeNode<T>[] {
const map = {};
const mapDepth = {};
const tree: TreeNode<T>[] = [];
@ -649,7 +649,7 @@ export class CoreUtilsProvider {
if (fallbackLang === defaultLang) {
// Same language, just reject.
return Promise.reject('Countries not found.');
throw new Error('Countries not found.');
}
return this.getCountryKeysListForLanguage(fallbackLang);
@ -786,7 +786,7 @@ export class CoreUtilsProvider {
* @param value Value to check.
* @return Whether the value is false, 0 or "0".
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
isFalseOrZero(value: any): boolean {
return typeof value != 'undefined' && (value === false || value === 'false' || parseInt(value, 10) === 0);
}
@ -797,7 +797,7 @@ export class CoreUtilsProvider {
* @param value Value to check.
* @return Whether the value is true, 1 or "1".
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
isTrueOrOne(value: any): boolean {
return typeof value != 'undefined' && (value === true || value === 'true' || parseInt(value, 10) === 1);
}
@ -808,7 +808,7 @@ export class CoreUtilsProvider {
* @param error Error to check.
* @return Whether the error was returned by the WebService.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
isWebServiceError(error: any): boolean {
return error && (typeof error.warningcode != 'undefined' || (typeof error.errorcode != 'undefined' &&
error.errorcode != 'invalidtoken' && error.errorcode != 'userdeleted' && error.errorcode != 'upgraderunning' &&
@ -827,8 +827,12 @@ export class CoreUtilsProvider {
* @param defaultValue Element that will become default option value. Default 0.
* @return The now assembled array
*/
makeMenuFromList<T>(list: string, defaultLabel?: string, separator: string = ',',
defaultValue?: T): { label: string; value: T | number }[] {
makeMenuFromList<T>(
list: string,
defaultLabel?: string,
separator: string = ',',
defaultValue?: T,
): { label: string; value: T | number }[] {
// Split and format the list.
const split = list.split(separator).map((label, index) => ({
label: label.trim(),
@ -863,7 +867,7 @@ export class CoreUtilsProvider {
* @param value Value to check.
* @return True if not null and not undefined.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
notNullOrUndefined(value: any): boolean {
return typeof value != 'undefined' && value !== null;
}
@ -1008,36 +1012,32 @@ export class CoreUtilsProvider {
* @param url The URL of the file.
* @return Promise resolved when opened.
*/
openOnlineFile(url: string): Promise<void> {
async openOnlineFile(url: string): Promise<void> {
if (CoreApp.instance.isAndroid()) {
// In Android we need the mimetype to open it.
return this.getMimeTypeFromUrl(url).catch(() => {
// Error getting mimetype, return undefined.
}).then((mimetype) => {
if (!mimetype) {
// Couldn't retrieve mimetype. Return error.
return Promise.reject(Translate.instance.instant('core.erroropenfilenoextension'));
}
const mimetype = await this.ignoreErrors(this.getMimeTypeFromUrl(url));
const options = {
action: WebIntent.instance.ACTION_VIEW,
url,
type: mimetype,
};
if (!mimetype) {
// Couldn't retrieve mimetype. Return error.
throw new Error(Translate.instance.instant('core.erroropenfilenoextension'));
}
return WebIntent.instance.startActivity(options).catch((error) => {
this.logger.error('Error opening online file ' + url + ' with mimetype ' + mimetype);
this.logger.error('Error: ', JSON.stringify(error));
const options = {
action: WebIntent.instance.ACTION_VIEW,
url,
type: mimetype,
};
return Promise.reject(Translate.instance.instant('core.erroropenfilenoapp'));
});
return WebIntent.instance.startActivity(options).catch((error) => {
this.logger.error('Error opening online file ' + url + ' with mimetype ' + mimetype);
this.logger.error('Error: ', JSON.stringify(error));
throw new Error(Translate.instance.instant('core.erroropenfilenoapp'));
});
}
// In the rest of platforms we need to open them in InAppBrowser.
this.openInApp(url);
return Promise.resolve();
}
/**
@ -1062,8 +1062,13 @@ export class CoreUtilsProvider {
* @param sortByValue True to sort values alphabetically, false otherwise.
* @return Array of objects with the name & value of each property.
*/
objectToArrayOfObjects(obj: Record<string, unknown>, keyName: string, valueName: string, sortByKey?: boolean,
sortByValue?: boolean): Record<string, unknown>[] {
objectToArrayOfObjects(
obj: Record<string, unknown>,
keyName: string,
valueName: string,
sortByKey?: boolean,
sortByValue?: boolean,
): Record<string, unknown>[] {
// Get the entries from an object or primitive value.
const getEntries = (elKey: string, value: unknown): Record<string, unknown>[] | unknown => {
if (typeof value == 'undefined' || value == null) {
@ -1123,8 +1128,12 @@ export class CoreUtilsProvider {
* @param keyPrefix Key prefix if neededs to delete it.
* @return Object.
*/
objectToKeyValueMap(objects: Record<string, unknown>[], keyName: string, valueName: string,
keyPrefix?: string): {[name: string]: unknown} {
objectToKeyValueMap(
objects: Record<string, unknown>[],
keyName: string,
valueName: string,
keyPrefix?: string,
): {[name: string]: unknown} {
if (!objects) {
return;
}
@ -1343,13 +1352,25 @@ export class CoreUtilsProvider {
*/
timeoutPromise<T>(promise: Promise<T>, time: number): Promise<T> {
return new Promise((resolve, reject): void => {
const timeout = setTimeout(() => {
reject({ timeout: true });
}, time);
let timedOut = false;
const resolveBeforeTimeout = () => {
if (timedOut) {
return;
}
resolve();
};
const timeout = setTimeout(
() => {
reject({ timeout: true });
timedOut = true;
},
time,
);
promise.then(resolve).catch(reject).finally(() => {
clearTimeout(timeout);
});
promise
.then(resolveBeforeTimeout)
.catch(reject)
.finally(() => clearTimeout(timeout));
});
}
@ -1362,7 +1383,7 @@ export class CoreUtilsProvider {
* @param strict If true, then check the input and return false if it is not a valid number.
* @return False if bad format, empty string if empty value or the parsed float if not.
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
unformatFloat(localeFloat: any, strict?: boolean): false | '' | number {
// Bad format on input type number.
if (typeof localeFloat == 'undefined') {
@ -1568,6 +1589,17 @@ export class CoreUtilsProvider {
return new Promise(resolve => setTimeout(resolve, milliseconds));
}
/**
* Override native window.open with InAppBrowser if available.
*/
private overrideWindowOpen() {
if (!window.cordova?.InAppBrowser) {
return;
}
window.open = window.cordova.InAppBrowser.open;
}
}
export class CoreUtils extends makeSingleton(CoreUtilsProvider) {}