MOBILE-3320 tsconfig: Use strict checks

main
Noel De Martin 2020-10-16 13:08:19 +02:00
parent b366486aaa
commit b83d1df97b
13 changed files with 249 additions and 202 deletions

View File

@ -148,6 +148,7 @@ var appConfig = {
'warn',
{
allowFinally: true,
terminationMethod: ['catch', 'finally'],
},
],
'arrow-body-style': ['error', 'as-needed'],

View File

@ -40,7 +40,7 @@ export class CoreSingletonsFactory {
/**
* Angular injector used to resolve singleton instances.
*/
private injector: Injector;
private injector?: Injector;
/**
* Set the injector that will be used to resolve instances in the singletons created with this factory.
@ -68,6 +68,10 @@ export class CoreSingletonsFactory {
static get instance(): Service {
// Initialize instances lazily.
if (!this.serviceInstance) {
if (!factory.injector) {
throw new Error('Can\'t resolve a singleton instance without an injector');
}
this.serviceInstance = factory.injector.get(injectionToken);
}

View File

@ -133,8 +133,8 @@ export interface SQLiteDBForeignKeySchema {
*/
export class SQLiteDB {
db: SQLiteObject;
promise: Promise<void>;
db?: SQLiteObject;
promise!: Promise<void>;
/**
* Create and open the database.
@ -164,7 +164,7 @@ export class SQLiteDB {
foreignKeys?: SQLiteDBForeignKeySchema[],
tableCheck?: string,
): string {
const columnsSql = [];
const columnsSql: string[] = [];
let sql = `CREATE TABLE IF NOT EXISTS ${name} (`;
// First define all the columns.
@ -225,8 +225,8 @@ export class SQLiteDB {
for (const index in foreignKeys) {
const foreignKey = foreignKeys[index];
if (!foreignKey.columns || !!foreignKey.columns.length) {
return;
if (!foreignKey?.columns.length) {
continue;
}
sql += `, FOREIGN KEY (${foreignKey.columns.join(', ')}) REFERENCES ${foreignKey.table} `;
@ -251,7 +251,7 @@ export class SQLiteDB {
async close(): Promise<void> {
await this.ready();
await this.db.close();
await this.db!.close();
}
/**
@ -348,10 +348,7 @@ export class SQLiteDB {
* @return Promise resolved when success.
*/
async createTablesFromSchema(tables: SQLiteDBTableSchema[]): Promise<void> {
const promises = [];
tables.forEach((table) => {
promises.push(this.createTableFromSchema(table));
});
const promises = tables.map(table => this.createTableFromSchema(table));
await Promise.all(promises);
}
@ -432,7 +429,7 @@ export class SQLiteDB {
async execute(sql: string, params?: SQLiteDBRecordValue[]): Promise<any> {
await this.ready();
return this.db.executeSql(sql, params);
return this.db!.executeSql(sql, params);
}
/**
@ -447,7 +444,7 @@ export class SQLiteDB {
async executeBatch(sqlStatements: (string | string[] | any)[]): Promise<void> {
await this.ready();
await this.db.sqlBatch(sqlStatements);
await this.db!.sqlBatch(sqlStatements);
}
/**
@ -532,7 +529,7 @@ export class SQLiteDB {
* @return Promise resolved with the field's value.
*/
async getFieldSql(sql: string, params?: SQLiteDBRecordValue[]): Promise<SQLiteDBRecordValue> {
const record = await this.getRecordSql(sql, params);
const record = await this.getRecordSql<Record<string, SQLiteDBRecordValue>>(sql, params);
if (!record) {
throw new CoreError('No record found.');
}
@ -552,7 +549,7 @@ export class SQLiteDB {
getInOrEqual(
items: SQLiteDBRecordValue | SQLiteDBRecordValue[],
equal: boolean = true,
onEmptyItems?: SQLiteDBRecordValue,
onEmptyItems?: SQLiteDBRecordValue | null,
): SQLiteDBQueryParams {
let sql = '';
let params: SQLiteDBRecordValue[];
@ -564,7 +561,7 @@ export class SQLiteDB {
// Handle onEmptyItems on empty array of items.
if (Array.isArray(items) && !items.length) {
if (onEmptyItems === null) { // Special case, NULL value.
if (onEmptyItems === null || typeof onEmptyItems === 'undefined') { // Special case, NULL value.
sql = equal ? ' IS NULL' : ' IS NOT NULL';
return { sql, params: [] };
@ -758,7 +755,7 @@ export class SQLiteDB {
const result = await this.execute(sql, params);
// Retrieve the records.
const records = [];
const records: T[] = [];
for (let i = 0; i < result.rows.length; i++) {
records.push(result.rows.item(i));
}
@ -868,7 +865,7 @@ export class SQLiteDB {
* @param limitNum How many results to return.
* @return Normalised limit params in array: [limitFrom, limitNum].
*/
normaliseLimitFromNum(limitFrom: number, limitNum: number): number[] {
normaliseLimitFromNum(limitFrom?: number, limitNum?: number): number[] {
// We explicilty treat these cases as 0.
if (!limitFrom || limitFrom === -1) {
limitFrom = 0;
@ -893,7 +890,7 @@ export class SQLiteDB {
async open(): Promise<void> {
await this.ready();
await this.db.open();
await this.db!.open();
}
/**
@ -994,11 +991,7 @@ export class SQLiteDB {
return 0;
}
const sets = [];
for (const key in data) {
sets.push(`${key} = ?`);
}
const sets = Object.keys(data).map(key => `${key} = ?`);
let sql = `UPDATE ${table} SET ${sets.join(', ')}`;
if (where) {
sql += ` WHERE ${where}`;
@ -1029,8 +1022,8 @@ export class SQLiteDB {
};
}
const where = [];
const params = [];
const where: string[] = [];
const params: SQLiteDBRecordValue[] = [];
for (const key in conditions) {
const value = conditions[key];
@ -1064,7 +1057,7 @@ export class SQLiteDB {
};
}
const params = [];
const params: SQLiteDBRecordValue[] = [];
let sql = '';
values.forEach((value) => {

View File

@ -20,7 +20,6 @@ import { SQLiteDB } from '@classes/sqlitedb';
* Class to mock the interaction with the SQLite database.
*/
export class SQLiteDBMock extends SQLiteDB {
promise: Promise<void>;
/**
* Create and open the database.
@ -46,9 +45,11 @@ export class SQLiteDBMock extends SQLiteDB {
*
* @return Promise resolved when done.
*/
emptyDatabase(): Promise<any> {
async emptyDatabase(): Promise<any> {
await this.ready();
return new Promise((resolve, reject): void => {
this.db.transaction((tx) => {
this.db!.transaction((tx) => {
// Query all tables from sqlite_master that we have created and can modify.
const args = [];
const query = `SELECT * FROM sqlite_master
@ -63,7 +64,7 @@ export class SQLiteDBMock extends SQLiteDB {
}
// Drop all the tables.
const promises = [];
const promises: Promise<void>[] = [];
for (let i = 0; i < result.rows.length; i++) {
promises.push(new Promise((resolve, reject): void => {
@ -73,7 +74,7 @@ export class SQLiteDBMock extends SQLiteDB {
}));
}
Promise.all(promises).then(resolve, reject);
Promise.all(promises).then(resolve).catch(reject);
}, reject);
});
});
@ -88,14 +89,18 @@ export class SQLiteDBMock extends SQLiteDB {
* @param params Query parameters.
* @return Promise resolved with the result.
*/
execute(sql: string, params?: any[]): Promise<any> {
async execute(sql: string, params?: any[]): Promise<any> {
await this.ready();
return new Promise((resolve, reject): void => {
// With WebSQL, all queries must be run in a transaction.
this.db.transaction((tx) => {
this.db!.transaction((tx) => {
tx.executeSql(sql, params, (tx, results) => {
resolve(results);
}, (tx, error) => {
// eslint-disable-next-line no-console
console.error(sql, params, error);
reject(error);
});
});
@ -110,11 +115,13 @@ export class SQLiteDBMock extends SQLiteDB {
* @param sqlStatements SQL statements to execute.
* @return Promise resolved with the result.
*/
executeBatch(sqlStatements: any[]): Promise<any> {
async executeBatch(sqlStatements: any[]): Promise<any> {
await this.ready();
return new Promise((resolve, reject): void => {
// Create a transaction to execute the queries.
this.db.transaction((tx) => {
const promises = [];
this.db!.transaction((tx) => {
const promises: Promise<void>[] = [];
// Execute all the queries. Each statement can be a string or an array.
sqlStatements.forEach((statement) => {
@ -133,7 +140,9 @@ export class SQLiteDBMock extends SQLiteDB {
tx.executeSql(query, params, (tx, results) => {
resolve(results);
}, (tx, error) => {
// eslint-disable-next-line no-console
console.error(query, params, error);
reject(error);
});
}));

View File

@ -45,13 +45,13 @@ export class CoreAppProvider {
protected db: SQLiteDB;
protected logger: CoreLogger;
protected ssoAuthenticationDeferred: PromiseDefer<void>;
protected ssoAuthenticationDeferred?: PromiseDefer<void>;
protected isKeyboardShown = false;
protected keyboardOpening = false;
protected keyboardClosing = false;
protected backActions: {callback: () => boolean; priority: number}[] = [];
protected mainMenuId = 0;
protected mainMenuOpen: number;
protected mainMenuOpen?: number;
protected forceOffline = false;
// Variables for DB.
@ -224,7 +224,7 @@ export class CoreAppProvider {
* @param storesConfig Config params to send the user to the right place.
* @return Store URL.
*/
getAppStoreUrl(storesConfig: CoreStoreConfig): string {
getAppStoreUrl(storesConfig: CoreStoreConfig): string | null {
if (this.isMac() && storesConfig.mac) {
return 'itms-apps://itunes.apple.com/app/' + storesConfig.mac;
}
@ -332,6 +332,8 @@ export class CoreAppProvider {
try {
// @todo return require('os').platform().indexOf('linux') === 0;
return false;
} catch (ex) {
return false;
}
@ -349,6 +351,8 @@ export class CoreAppProvider {
try {
// @todo return require('os').platform().indexOf('darwin') === 0;
return false;
} catch (ex) {
return false;
}
@ -439,6 +443,8 @@ export class CoreAppProvider {
try {
// @todo return require('os').platform().indexOf('win') === 0;
return false;
} catch (ex) {
return false;
}
@ -521,7 +527,9 @@ export class CoreAppProvider {
* @return Promise resolved once SSO authentication finishes.
*/
async waitForSSOAuthentication(): Promise<void> {
await this.ssoAuthenticationDeferred && this.ssoAuthenticationDeferred.promise;
const promise = this.ssoAuthenticationDeferred?.promise;
await promise;
}
/**
@ -530,7 +538,7 @@ export class CoreAppProvider {
* @param timeout Maximum time to wait, use null to wait forever.
*/
async waitForResume(timeout: number | null = null): Promise<void> {
let deferred = CoreUtils.instance.promiseDefer<void>();
let deferred: PromiseDefer<void> | null = CoreUtils.instance.promiseDefer<void>();
const stopWaiting = () => {
if (!deferred) {
@ -556,13 +564,13 @@ export class CoreAppProvider {
* @return Object with siteid, state, params and timemodified.
*/
getRedirect<Params extends Record<string, unknown> = Record<string, unknown>>(): CoreRedirectData<Params> {
if (localStorage && localStorage.getItem) {
if (localStorage?.getItem) {
try {
const paramsJson = localStorage.getItem('CoreRedirectParams');
const data: CoreRedirectData<Params> = {
siteId: localStorage.getItem('CoreRedirectSiteId'),
page: localStorage.getItem('CoreRedirectState'),
timemodified: parseInt(localStorage.getItem('CoreRedirectTime'), 10),
siteId: localStorage.getItem('CoreRedirectSiteId') || undefined,
page: localStorage.getItem('CoreRedirectState') || undefined,
timemodified: parseInt(localStorage.getItem('CoreRedirectTime') || '0', 10),
};
if (paramsJson) {

View File

@ -14,7 +14,7 @@
import { Injectable } from '@angular/core';
import { CoreUtils, PromiseDefer } from '@services/utils/utils';
import { CoreUtils, PromiseDefer, OrderedPromiseData } from '@services/utils/utils';
import { CoreLogger } from '@singletons/logger';
import { makeSingleton } from '@singletons/core.singletons';
@ -56,7 +56,7 @@ export class CoreInitDelegate {
protected initProcesses: { [s: string]: CoreInitHandler } = {};
protected logger: CoreLogger;
protected readiness: CoreInitReadinessPromiseDefer<void>;
protected readiness?: CoreInitReadinessPromiseDefer<void>;
constructor() {
this.logger = CoreLogger.getInstance('CoreInitDelegate');
@ -68,7 +68,7 @@ export class CoreInitDelegate {
* Reserved for core use, do not call directly.
*/
executeInitProcesses(): void {
let ordered = [];
const ordered: CoreInitHandler[] = [];
if (typeof this.readiness == 'undefined') {
this.initReadiness();
@ -78,15 +78,15 @@ export class CoreInitDelegate {
for (const name in this.initProcesses) {
ordered.push(this.initProcesses[name]);
}
ordered.sort((a, b) => b.priority - a.priority);
ordered.sort((a, b) => (b.priority || 0) - (a.priority || 0));
ordered = ordered.map((data: CoreInitHandler) => ({
const orderedPromises: OrderedPromiseData[] = ordered.map((data: CoreInitHandler) => ({
function: this.prepareProcess.bind(this, data),
blocking: !!data.blocking,
}));
// Execute all the processes in order to solve dependencies.
CoreUtils.instance.executeOrderedPromises(ordered).finally(this.readiness.resolve);
CoreUtils.instance.executeOrderedPromises(orderedPromises).finally(this.readiness!.resolve);
}
/**
@ -94,7 +94,9 @@ export class CoreInitDelegate {
*/
protected initReadiness(): void {
this.readiness = CoreUtils.instance.promiseDefer();
this.readiness.promise.then(() => this.readiness.resolved = true);
// eslint-disable-next-line promise/catch-or-return
this.readiness.promise.then(() => this.readiness!.resolved = true);
}
/**
@ -103,7 +105,7 @@ export class CoreInitDelegate {
* @return Whether it's ready.
*/
isReady(): boolean {
return this.readiness.resolved;
return this.readiness?.resolved || false;
}
/**
@ -133,7 +135,7 @@ export class CoreInitDelegate {
this.initReadiness();
}
await this.readiness.promise;
await this.readiness!.promise;
}
/**

View File

@ -30,9 +30,9 @@ export class CoreLangProvider {
protected fallbackLanguage = 'en'; // Always use English as fallback language since it contains all strings.
protected defaultLanguage = CoreConfigConstants.default_lang || 'en'; // Lang to use if device lang not valid or is forced.
protected currentLanguage: string; // Save current language in a variable to speed up the get function.
protected currentLanguage?: string; // Save current language in a variable to speed up the get function.
protected customStrings: CoreLanguageObject = {}; // Strings defined using the admin tool.
protected customStringsRaw: string;
protected customStringsRaw?: string;
protected sitePluginsStrings: CoreLanguageObject = {}; // Strings defined by site plugins.
constructor() {
@ -123,7 +123,7 @@ export class CoreLangProvider {
* @return Promise resolved when the change is finished.
*/
async changeCurrentLanguage(language: string): Promise<void> {
const promises = [];
const promises: Promise<unknown>[] = [];
// Change the language, resolving the promise when we receive the first value.
promises.push(new Promise((resolve, reject) => {
@ -363,8 +363,8 @@ export class CoreLangProvider {
if (currentLangChanged) {
// Some lang strings have changed, emit an event to update the pipes.
Translate.instance.onLangChange.emit({
lang: this.currentLanguage,
translations: Translate.instance.translations[this.currentLanguage],
lang: this.currentLanguage!,
translations: Translate.instance.translations[this.currentLanguage!],
});
}
}

View File

@ -113,7 +113,7 @@ export class CoreIframeUtilsProvider {
}
// Remove the warning and show the iframe
CoreDomUtils.instance.removeElement(element.parentElement, 'div.core-iframe-offline-warning');
CoreDomUtils.instance.removeElement(element.parentElement!, 'div.core-iframe-offline-warning');
element.classList.remove('core-iframe-offline-disabled');
if (isSubframe) {
@ -131,9 +131,9 @@ export class CoreIframeUtilsProvider {
* @param element Element to treat (iframe, embed, ...).
* @return Window and Document.
*/
getContentWindowAndDocument(element: CoreFrameElement): { window: Window; document: Document } {
let contentWindow: Window = 'contentWindow' in element ? element.contentWindow : undefined;
let contentDocument: Document;
getContentWindowAndDocument(element: CoreFrameElement): { window: Window | null; document: Document | null } {
let contentWindow: Window | null = 'contentWindow' in element ? element.contentWindow : null;
let contentDocument: Document | null = null;
try {
contentDocument = 'contentDocument' in element && element.contentDocument
@ -209,7 +209,8 @@ export class CoreIframeUtilsProvider {
contentWindow.open = (url: string, name: string) => {
this.windowOpen(url, name, element, navCtrl);
return null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return null as any;
};
}
@ -233,31 +234,35 @@ export class CoreIframeUtilsProvider {
* @param navCtrl NavController to use if a link can be opened in the app.
*/
treatFrame(element: CoreFrameElement, isSubframe?: boolean, navCtrl?: NavController): void {
if (element) {
if (!element) {
return;
}
const treatElement = (sendResizeEvent: boolean = false) => {
this.checkOnlineFrameInOffline(element, isSubframe);
let winAndDoc = this.getContentWindowAndDocument(element);
const { window, document } = this.getContentWindowAndDocument(element);
// Redefine window.open in this element and sub frames, it might have been loaded already.
this.redefineWindowOpen(element, winAndDoc.window, winAndDoc.document, navCtrl);
if (window && document) {
this.redefineWindowOpen(element, window, document, navCtrl);
}
// Treat links.
this.treatFrameLinks(element, winAndDoc.document);
if (document) {
this.treatFrameLinks(element, document);
}
element.addEventListener('load', () => {
this.checkOnlineFrameInOffline(element, isSubframe);
// Send a resize events to the iframe so it calculates the right size if needed.
if (window && sendResizeEvent) {
setTimeout(() => window.dispatchEvent(new Event('resize')), 1000);
}
};
// Element loaded, redefine window.open and treat links again.
winAndDoc = this.getContentWindowAndDocument(element);
this.redefineWindowOpen(element, winAndDoc.window, winAndDoc.document, navCtrl);
this.treatFrameLinks(element, winAndDoc.document);
treatElement();
if (winAndDoc.window) {
// Send a resize events to the iframe so it calculates the right size if needed.
setTimeout(() => {
winAndDoc.window.dispatchEvent(new Event('resize'));
}, 1000);
}
});
}
// Element loaded, redefine window.open and treat links again.
element.addEventListener('load', () => treatElement(true));
}
/**
@ -279,7 +284,7 @@ export class CoreIframeUtilsProvider {
}
// Find the link being clicked.
let el = <Element> event.target;
let el: Element | null = event.target as Element;
while (el && el.tagName !== 'A') {
el = el.parentElement;
}
@ -386,19 +391,24 @@ export class CoreIframeUtilsProvider {
}
const urlParts = CoreUrl.parse(link.href);
if (!link.href || (urlParts.protocol && urlParts.protocol == 'javascript')) {
if (!link.href || !urlParts || (urlParts.protocol && urlParts.protocol == 'javascript')) {
// Links with no URL and Javascript links are ignored.
return;
}
if (!CoreUrlUtils.instance.isLocalFileUrlScheme(urlParts.protocol)) {
if (urlParts.protocol && !CoreUrlUtils.instance.isLocalFileUrlScheme(urlParts.protocol)) {
// Scheme suggests it's an external resource.
event && event.preventDefault();
const frameSrc = element && ((<HTMLFrameElement> element).src || (<HTMLObjectElement> element).data);
// If the frame is not local, check the target to identify how to treat the link.
if (element && !CoreUrlUtils.instance.isLocalFileUrl(frameSrc) && (!link.target || link.target == '_self')) {
if (
element &&
frameSrc &&
!CoreUrlUtils.instance.isLocalFileUrl(frameSrc) &&
(!link.target || link.target == '_self')
) {
// Load the link inside the frame itself.
if (element.tagName.toLowerCase() == 'object') {
element.setAttribute('data', link.href);
@ -455,8 +465,8 @@ export class CoreIframeUtilsProvider {
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({
userScriptWindow.WKUserScript?.addScript({ id: 'CoreIframeUtilsLinksScript', file: linksPath });
userScriptWindow.WKUserScript?.addScript({
id: 'CoreIframeUtilsRecaptchaScript',
file: recaptchaPath,
injectionTime: WKUserScriptInjectionTime.END,

View File

@ -116,7 +116,7 @@ export class CoreMimetypeUtilsProvider {
*/
protected fillGroupMimeInfo(group: string): void {
const mimetypes = {}; // Use an object to prevent duplicates.
const extensions = []; // Extensions are unique.
const extensions: string[] = []; // Extensions are unique.
for (const extension in this.extToMime) {
const data = this.extToMime[extension];
@ -140,13 +140,13 @@ export class CoreMimetypeUtilsProvider {
* @param url URL of the file. It will be used if there's more than one possible extension.
* @return Extension.
*/
getExtension(mimetype: string, url?: string): string {
getExtension(mimetype: string, url?: string): string | undefined {
mimetype = mimetype || '';
mimetype = mimetype.split(';')[0]; // Remove codecs from the mimetype if any.
if (mimetype == 'application/x-forcedownload' || mimetype == 'application/forcedownload') {
// Couldn't get the right mimetype, try to guess it.
return this.guessExtensionFromUrl(url);
return url && this.guessExtensionFromUrl(url);
}
const extensions = this.mimeToExt[mimetype];
@ -154,7 +154,7 @@ export class CoreMimetypeUtilsProvider {
if (extensions.length > 1 && url) {
// There's more than one possible extension. Check if the URL has extension.
const candidate = this.guessExtensionFromUrl(url);
if (extensions.indexOf(candidate) != -1) {
if (candidate && extensions.indexOf(candidate) != -1) {
return candidate;
}
}
@ -173,19 +173,22 @@ export class CoreMimetypeUtilsProvider {
const filename = CoreUtils.instance.isFileEntry(file) ? (file as FileEntry).name : file.filename;
const extension = !CoreUtils.instance.isFileEntry(file) && file.mimetype
? this.getExtension(file.mimetype)
: this.getFileExtension(filename);
const mimeType = !CoreUtils.instance.isFileEntry(file) && file.mimetype ? file.mimetype : this.getMimeType(extension);
: (filename && this.getFileExtension(filename));
const mimeType = !CoreUtils.instance.isFileEntry(file) && file.mimetype
? file.mimetype
: (extension && this.getMimeType(extension));
// @todo linting: See if this can be removed
(file as CoreWSExternalFile).mimetype = mimeType;
if (this.canBeEmbedded(extension)) {
if (extension && this.canBeEmbedded(extension)) {
const embedType = this.getExtensionType(extension);
// @todo linting: See if this can be removed
(file as { embedType: string }).embedType = embedType;
(file as { embedType?: string }).embedType = embedType;
path = CoreFile.instance.convertFileSrc(path ?? (CoreUtils.instance.isFileEntry(file) ? file.toURL() : file.fileurl));
path = path ?? (CoreUtils.instance.isFileEntry(file) ? file.toURL() : file.fileurl);
path = path && CoreFile.instance.convertFileSrc(path);
switch (embedType) {
case 'image':
@ -223,7 +226,7 @@ export class CoreMimetypeUtilsProvider {
* @param extension Extension.
* @return Icon. Undefined if not found.
*/
getExtensionIconName(extension: string): string {
getExtensionIconName(extension: string): string | undefined {
if (this.extToMime[extension]) {
if (this.extToMime[extension].icon) {
return this.extToMime[extension].icon;
@ -242,7 +245,7 @@ export class CoreMimetypeUtilsProvider {
* @param extension Extension.
* @return Type of the extension.
*/
getExtensionType(extension: string): string {
getExtensionType(extension: string): string | undefined {
extension = this.cleanExtension(extension);
if (this.extToMime[extension] && this.extToMime[extension].string) {
@ -270,8 +273,8 @@ export class CoreMimetypeUtilsProvider {
* @return The path to a file icon.
*/
getFileIcon(filename: string): string {
const ext = this.getFileExtension(filename);
const icon = this.getExtensionIconName(ext) || 'unknown';
const extension = this.getFileExtension(filename);
const icon = (extension && this.getExtensionIconName(extension)) || 'unknown';
return this.getFileIconForType(icon);
}
@ -302,14 +305,14 @@ export class CoreMimetypeUtilsProvider {
* @param fileUrl The file URL.
* @return The lowercased extension without the dot, or undefined.
*/
guessExtensionFromUrl(fileUrl: string): string {
guessExtensionFromUrl(fileUrl: string): string | undefined {
const split = fileUrl.split('.');
let candidate;
let extension;
let position;
if (split.length > 1) {
candidate = split.pop().toLowerCase();
candidate = split.pop()!.toLowerCase();
// Remove params if any.
position = candidate.indexOf('?');
if (position > -1) {
@ -338,7 +341,7 @@ export class CoreMimetypeUtilsProvider {
* @param filename The file name.
* @return The lowercased extension, or undefined.
*/
getFileExtension(filename: string): string {
getFileExtension(filename: string): string | undefined {
const dot = filename.lastIndexOf('.');
let ext;
@ -382,7 +385,7 @@ export class CoreMimetypeUtilsProvider {
* @param extension Extension.
* @return Mimetype.
*/
getMimeType(extension: string): string {
getMimeType(extension: string): string | undefined {
extension = this.cleanExtension(extension);
if (this.extToMime[extension] && this.extToMime[extension].type) {
@ -400,9 +403,9 @@ export class CoreMimetypeUtilsProvider {
*/
getMimetypeDescription(obj: FileEntry | { filename: string; mimetype: string } | string, capitalise?: boolean): string {
const langPrefix = 'assets.mimetypes.';
let filename = '';
let mimetype = '';
let extension = '';
let filename: string | undefined = '';
let mimetype: string | undefined = '';
let extension: string | undefined = '';
if (typeof obj == 'object' && CoreUtils.instance.isFileEntry(obj)) {
// It's a FileEntry. Don't use the file function because it's asynchronous and the type isn't reliable.
@ -419,7 +422,7 @@ export class CoreMimetypeUtilsProvider {
if (!mimetype) {
// Try to calculate the mimetype using the extension.
mimetype = this.getMimeType(extension);
mimetype = extension && this.getMimeType(extension);
}
}
@ -478,7 +481,7 @@ export class CoreMimetypeUtilsProvider {
* @param mimetype Mimetype.
* @return Type of the mimetype.
*/
getMimetypeType(mimetype: string): string {
getMimetypeType(mimetype: string): string | undefined {
mimetype = mimetype.split(';')[0]; // Remove codecs from the mimetype if any.
const extensions = this.mimeToExt[mimetype];
@ -542,9 +545,9 @@ export class CoreMimetypeUtilsProvider {
isExtensionInGroup(extension: string, groups: string[]): boolean {
extension = this.cleanExtension(extension);
if (groups && groups.length && this.extToMime[extension] && this.extToMime[extension].groups) {
for (let i = 0; i < this.extToMime[extension].groups.length; i++) {
const group = this.extToMime[extension].groups[i];
if (groups?.length && this.extToMime[extension]?.groups) {
for (let i = 0; i < this.extToMime[extension].groups!.length; i++) {
const group = this.extToMime[extension].groups![i];
if (groups.indexOf(group) != -1) {
return true;
}

View File

@ -175,11 +175,11 @@ export class CoreTextUtilsProvider {
// Filter invalid messages, and convert them to messages in case they're errors.
const messages: string[] = [];
paragraphs.forEach((paragraph) => {
paragraphs.forEach(paragraph => {
// If it's an error, get its message.
const message = this.getErrorMessageFromError(paragraph);
if (paragraph) {
if (paragraph && message) {
messages.push(message);
}
});
@ -248,8 +248,7 @@ export class CoreTextUtilsProvider {
// First, we use a regexpr.
text = text.replace(/(<([^>]+)>)/ig, '');
// Then, we rely on the browser. We need to wrap the text to be sure is HTML.
const element = this.convertToElement(text);
text = element.textContent;
text = this.convertToElement(text).textContent!;
// Recover or remove new lines.
text = this.replaceNewLines(text, singleLine ? ' ' : '<br>');
@ -326,7 +325,7 @@ export class CoreTextUtilsProvider {
text = text.replace(/_/gi, ' ');
// This RegEx will detect any word change including Unicode chars. Some languages without spaces won't be counted fine.
return text.match(/\S+/gi).length;
return text.match(/\S+/gi)?.length || 0;
}
/**
@ -359,8 +358,7 @@ export class CoreTextUtilsProvider {
*/
decodeHTMLEntities(text: string): string {
if (text) {
const element = this.convertToElement(text);
text = element.textContent;
text = this.convertToElement(text).textContent!;
}
return text;
@ -512,7 +510,7 @@ export class CoreTextUtilsProvider {
if (clean) {
formatted = this.cleanTags(formatted, singleLine);
}
if (shortenLength > 0) {
if (shortenLength && shortenLength > 0) {
formatted = this.shortenText(formatted, shortenLength);
}
if (highlight) {
@ -529,10 +527,11 @@ export class CoreTextUtilsProvider {
* @param error Error object.
* @return Error message, undefined if not found.
*/
getErrorMessageFromError(error: string | CoreError | CoreTextErrorObject): string {
getErrorMessageFromError(error?: string | CoreError | CoreTextErrorObject): string | undefined {
if (typeof error == 'string') {
return error;
}
if (error instanceof CoreError) {
return error.message;
}
@ -546,12 +545,12 @@ export class CoreTextUtilsProvider {
* @param files Files to extract the URL from. They need to have the URL in a 'url' or 'fileurl' attribute.
* @return Pluginfile URL, undefined if no files found.
*/
getTextPluginfileUrl(files: CoreWSExternalFile[]): string {
if (files && files.length) {
getTextPluginfileUrl(files: CoreWSExternalFile[]): string | undefined {
if (files?.length) {
const url = files[0].fileurl;
// Remove text after last slash (encoded or not).
return url.substr(0, Math.max(url.lastIndexOf('/'), url.lastIndexOf('%2F')));
return url?.substr(0, Math.max(url.lastIndexOf('/'), url.lastIndexOf('%2F')));
}
return undefined;
@ -876,7 +875,7 @@ export class CoreTextUtilsProvider {
// Current lang not found. Try to find the first language.
const matches = text.match(anyLangRegEx);
if (matches && matches[0]) {
language = matches[0].match(/lang="([a-zA-Z0-9_-]+)"/)[1];
language = matches[0].match(/lang="([a-zA-Z0-9_-]+)"/)![1];
currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)</(?:lang|span)>', 'g');
} else {
// No multi-lang tag found, stop.

View File

@ -42,9 +42,9 @@ export class CoreUtilsProvider {
protected readonly DONT_CLONE = ['[object FileEntry]', '[object DirectoryEntry]', '[object DOMFileSystem]'];
protected logger: CoreLogger;
protected iabInstance: InAppBrowserObject;
protected iabInstance?: InAppBrowserObject;
protected uniqueIds: {[name: string]: number} = {};
protected qrScanData: {deferred: PromiseDefer<string>; observable: Subscription};
protected qrScanData?: {deferred: PromiseDefer<string>; observable: Subscription};
constructor(protected zone: NgZone) {
this.logger = CoreLogger.getInstance('CoreUtilsProvider');
@ -61,22 +61,14 @@ export class CoreUtilsProvider {
* @return New error message.
*/
addDataNotDownloadedError(error: Error | string, defaultError?: string): string {
let errorMessage = error;
const errorMessage = CoreTextUtils.instance.getErrorMessageFromError(error) || defaultError || '';
if (error && typeof error != 'string') {
errorMessage = CoreTextUtils.instance.getErrorMessageFromError(error);
if (this.isWebServiceError(error)) {
return errorMessage;
}
if (typeof errorMessage != 'string') {
errorMessage = defaultError || '';
}
if (!this.isWebServiceError(error)) {
// Local error. Add an extra warning.
errorMessage += '<br><br>' + Translate.instance.instant('core.errorsomedatanotdownloaded');
}
return errorMessage;
// Local error. Add an extra warning.
return errorMessage + '<br><br>' + Translate.instance.instant('core.errorsomedatanotdownloaded');
}
/**
@ -116,12 +108,16 @@ export class CoreUtilsProvider {
* @param result Object where to put the properties. If not defined, a new object will be created.
* @return The object.
*/
arrayToObject(array: unknown[], propertyName?: string, result?: unknown): unknown {
result = result || {};
array.forEach((entry) => {
arrayToObject<T extends Record<string, unknown> | string>(
array: T[],
propertyName?: string,
result: Record<string, T> = {},
): Record<string, T> {
for (const entry of array) {
const key = propertyName ? entry[propertyName] : entry;
result[key] = entry;
});
}
return result;
}
@ -144,7 +140,7 @@ export class CoreUtilsProvider {
maxLevels: number = 0,
level: number = 0,
undefinedIsNull: boolean = true,
): boolean {
): boolean | undefined {
if (typeof itemA == 'function' || typeof itemB == 'function') {
return true; // Don't compare functions.
} else if (typeof itemA == 'object' && typeof itemB == 'object') {
@ -266,9 +262,9 @@ export class CoreUtilsProvider {
}
return newArray;
} else if (typeof source == 'object' && source !== null) {
} else if (this.isObject(source)) {
// Check if the object shouldn't be copied.
if (source && source.toString && this.DONT_CLONE.indexOf(source.toString()) != -1) {
if (source.toString && this.DONT_CLONE.indexOf(source.toString()) != -1) {
// Object shouldn't be copied, return it as it is.
return source;
}
@ -365,7 +361,7 @@ export class CoreUtilsProvider {
* @return Promise resolved when all promises are resolved.
*/
executeOrderedPromises(orderedPromisesData: OrderedPromiseData[]): Promise<void> {
const promises = [];
const promises: Promise<void>[] = [];
let dependency = Promise.resolve();
// Execute all the processes in order.
@ -465,8 +461,8 @@ export class CoreUtilsProvider {
checkAll?: boolean,
...args: P
): Promise<string[]> {
const promises = [];
const enabledSites = [];
const promises: Promise<false | number>[] = [];
const enabledSites: string[] = [];
for (const i in siteIds) {
const siteId = siteIds[i];
@ -626,7 +622,7 @@ export class CoreUtilsProvider {
// Get the keys of the countries.
return this.getCountryList().then((countries) => {
// Sort translations.
const sortedCountries = [];
const sortedCountries: { code: string; name: string }[] = [];
Object.keys(countries).sort((a, b) => countries[a].localeCompare(countries[b])).forEach((key) => {
sortedCountries.push({ code: key, name: countries[key] });
@ -669,7 +665,7 @@ export class CoreUtilsProvider {
const table = await CoreLang.instance.getTranslationTable(lang);
// Gather all the keys for countries,
const keys = [];
const keys: string[] = [];
for (const name in table) {
if (name.indexOf('assets.countries.') === 0) {
@ -696,7 +692,7 @@ export class CoreUtilsProvider {
getMimeTypeFromUrl(url: string): Promise<string> {
// First check if it can be guessed from the URL.
const extension = CoreMimetypeUtils.instance.guessExtensionFromUrl(url);
const mimetype = CoreMimetypeUtils.instance.getMimeType(extension);
const mimetype = extension && CoreMimetypeUtils.instance.getMimeType(extension);
if (mimetype) {
return Promise.resolve(mimetype);
@ -730,6 +726,16 @@ export class CoreUtilsProvider {
return 'isFile' in file;
}
/**
* Check if a value is an object.
*
* @param object Variable.
* @return Type guard indicating if this is an object.
*/
isObject(object: unknown): object is Record<string, unknown> {
return typeof object === 'object' && object !== null;
}
/**
* Given a list of files, check if there are repeated names.
*
@ -741,12 +747,12 @@ export class CoreUtilsProvider {
return false;
}
const names = [];
const names: string[] = [];
// Check if there are 2 files with the same name.
for (let i = 0; i < files.length; i++) {
const file = files[i];
const name = this.isFileEntry(file) ? file.name : file.filename;
const name = (this.isFileEntry(file) ? file.name : file.filename) || '';
if (names.indexOf(name) > -1) {
return Translate.instance.instant('core.filenameexist', { $a: name });
@ -885,7 +891,7 @@ export class CoreUtilsProvider {
path = CoreFile.instance.unconvertFileSrc(path);
const extension = CoreMimetypeUtils.instance.getFileExtension(path);
const mimetype = CoreMimetypeUtils.instance.getMimeType(extension);
const mimetype = extension && CoreMimetypeUtils.instance.getMimeType(extension);
if (mimetype == 'text/html' && CoreApp.instance.isAndroid()) {
// Open HTML local files in InAppBrowser, in system browser some embedded files aren't loaded.
@ -902,7 +908,7 @@ export class CoreUtilsProvider {
}
try {
await FileOpener.instance.open(path, mimetype);
await FileOpener.instance.open(path, mimetype || '');
} catch (error) {
this.logger.error('Error opening file ' + path + ' with mimetype ' + mimetype);
this.logger.error('Error: ', JSON.stringify(error));
@ -924,7 +930,7 @@ export class CoreUtilsProvider {
* @param options Override default options passed to InAppBrowser.
* @return The opened window.
*/
openInApp(url: string, options?: InAppBrowserOptions): InAppBrowserObject {
openInApp(url: string, options?: InAppBrowserOptions): InAppBrowserObject | undefined {
if (!url) {
return;
}
@ -950,7 +956,7 @@ export class CoreUtilsProvider {
if (CoreApp.instance.isDesktop() || CoreApp.instance.isMobile()) {
let loadStopSubscription;
const loadStartUrls = [];
const loadStartUrls: string[] = [];
// Trigger global events when a url is loaded or the window is closed. This is to make it work like in Ionic 1.
const loadStartSubscription = this.iabInstance.on('loadstart').subscribe((event) => {
@ -1076,10 +1082,10 @@ export class CoreUtilsProvider {
if (typeof value == 'undefined' || value == null) {
// Filter undefined and null values.
return;
} else if (typeof value == 'object') {
} else if (this.isObject(value)) {
// It's an object, return at least an entry for each property.
const keys = Object.keys(value);
let entries = [];
let entries: unknown[] = [];
keys.forEach((key) => {
const newElKey = elKey ? elKey + '[' + key + ']' : key;
@ -1110,9 +1116,9 @@ export class CoreUtilsProvider {
if (sortByKey || sortByValue) {
return entries.sort((a, b) => {
if (sortByKey) {
return a[keyName] >= b[keyName] ? 1 : -1;
return (a[keyName] as number) >= (b[keyName] as number) ? 1 : -1;
} else {
return a[valueName] >= b[valueName] ? 1 : -1;
return (a[valueName] as number) >= (b[valueName] as number) ? 1 : -1;
}
});
}
@ -1135,7 +1141,7 @@ export class CoreUtilsProvider {
keyName: string,
valueName: string,
keyPrefix?: string,
): {[name: string]: unknown} {
): {[name: string]: unknown} | undefined {
if (!objects) {
return;
}
@ -1206,13 +1212,13 @@ export class CoreUtilsProvider {
* @return The deferred promise.
*/
promiseDefer<T>(): PromiseDefer<T> {
const deferred: PromiseDefer<T> = {};
const deferred: Partial<PromiseDefer<T>> = {};
deferred.promise = new Promise((resolve, reject): void => {
deferred.resolve = resolve;
deferred.reject = reject;
});
return deferred;
return deferred as PromiseDefer<T>;
}
/**
@ -1257,7 +1263,11 @@ export class CoreUtilsProvider {
* @param key Key to check.
* @return Whether the two objects/arrays have the same value (or lack of one) for a given key.
*/
sameAtKeyMissingIsBlank(obj1: unknown, obj2: unknown, key: string): boolean {
sameAtKeyMissingIsBlank(
obj1: Record<string, unknown> | unknown[],
obj2: Record<string, unknown> | unknown[],
key: string,
): boolean {
let value1 = typeof obj1[key] != 'undefined' ? obj1[key] : '';
let value2 = typeof obj2[key] != 'undefined' ? obj2[key] : '';
@ -1426,19 +1436,19 @@ export class CoreUtilsProvider {
* @return Array without duplicate values.
*/
uniqueArray<T>(array: T[], key?: string): T[] {
const filtered = [];
const unique = {}; // Use an object to make it faster to check if it's duplicate.
array.forEach((entry) => {
return array.filter(entry => {
const value = key ? entry[key] : entry;
if (!unique[value]) {
if (value in unique) {
unique[value] = true;
filtered.push(entry);
}
});
return filtered;
return true;
}
return false;
});
}
/**
@ -1487,7 +1497,14 @@ export class CoreUtilsProvider {
*
* @return Promise resolved with the QR string, rejected if error or cancelled.
*/
async startScanQR(): Promise<string> {
async startScanQR(): Promise<string | undefined> {
try {
return this.startScanQR();
} catch (error) {
// do nothing
}
if (!CoreApp.instance.isMobile()) {
return Promise.reject('QRScanner isn\'t available in desktop apps.');
}
@ -1613,21 +1630,21 @@ export type PromiseDefer<T> = {
/**
* The promise.
*/
promise?: Promise<T>;
promise: Promise<T>;
/**
* Function to resolve the promise.
*
* @param value The resolve value.
*/
resolve?: (value?: T) => void; // Function to resolve the promise.
resolve: (value?: T) => void; // Function to resolve the promise.
/**
* Function to reject the promise.
*
* @param reason The reject param.
*/
reject?: (reason?: unknown) => void;
reject: (reason?: unknown) => void;
};
/**

View File

@ -15,6 +15,12 @@
import moment from 'moment';
import { environment } from '@/environments/environment';
/**
* Log function type.
*/
type LogFunction = (...data: unknown[]) => void;
/**
* Helper service to display messages in the console.
*
@ -34,9 +40,13 @@ export class CoreLogger {
debug: LogFunction;
error: LogFunction;
// Avoid creating singleton instances.
private constructor() {
// Nothing to do.
// Avoid creating instances.
private constructor(log: LogFunction, info: LogFunction, warn: LogFunction, debug: LogFunction, error: LogFunction) {
this.log = log;
this.info = info;
this.warn = warn;
this.debug = debug;
this.error = error;
}
/**
@ -54,29 +64,23 @@ export class CoreLogger {
// eslint-disable-next-line @typescript-eslint/no-empty-function
const muted = () => {};
return {
log: muted,
info: muted,
warn: muted,
debug: muted,
error: muted,
};
return new CoreLogger(muted, muted, muted, muted, muted);
}
className = className || '';
return {
return new CoreLogger(
// eslint-disable-next-line no-console
log: CoreLogger.prepareLogFn(console.log.bind(console), className),
CoreLogger.prepareLogFn(console.log.bind(console), className),
// eslint-disable-next-line no-console
info: CoreLogger.prepareLogFn(console.info.bind(console), className),
CoreLogger.prepareLogFn(console.info.bind(console), className),
// eslint-disable-next-line no-console
warn: CoreLogger.prepareLogFn(console.warn.bind(console), className),
CoreLogger.prepareLogFn(console.warn.bind(console), className),
// eslint-disable-next-line no-console
debug: CoreLogger.prepareLogFn(console.debug.bind(console), className),
CoreLogger.prepareLogFn(console.debug.bind(console), className),
// eslint-disable-next-line no-console
error: CoreLogger.prepareLogFn(console.error.bind(console), className),
};
CoreLogger.prepareLogFn(console.error.bind(console), className),
);
}
/**
@ -96,8 +100,3 @@ export class CoreLogger {
}
}
/**
* Log function type.
*/
type LogFunction = (...data: unknown[]) => void;

View File

@ -7,6 +7,8 @@
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"strictNullChecks": true,
"strictPropertyInitialization": true,
"module": "esnext",
"moduleResolution": "node",
"importHelpers": true,