Commit 07971b1e authored by Andreas Fernandez's avatar Andreas Fernandez Committed by Frank Nägler
Browse files

[TASK] Use ESLint as base for linting

TSLint is in a deprecation phase, recommending to migrate to ESLint
instead. This patch removes all TSLint packages and supplies a proper
ESLint configuration.

Also, rule violations are fixed in this patch.

Executed commands:

  yarn add --dev typescript-eslint \
    @typescript-eslint/parser \
    @typescript-eslint/eslint-plugin \
    eslint grunt-eslint

  yarn remove tslint grunt-tslint

  ./node_modules/.bin/eslint -c eslintrc.js --fix --ext .ts \
    ./Sources/TypeScript/

Resolves: #89232
Releases: master
Change-Id: I3bd4a1c30ecc27f8c334951547aff5e9352629da
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/61784


Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: Anja Leichsenring's avatarAnja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Frank Nägler's avatarFrank Nägler <frank.naegler@typo3.org>
Reviewed-by: Anja Leichsenring's avatarAnja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Frank Nägler's avatarFrank Nägler <frank.naegler@typo3.org>
parent cf40e083
......@@ -185,10 +185,9 @@ module.exports = function (grunt) {
ts: ((process.platform === 'win32') ? 'node_modules\\.bin\\tsc.cmd' : './node_modules/.bin/tsc') + ' --project tsconfig.json',
'yarn-install': 'yarn install'
},
tslint: {
eslint: {
options: {
configuration: 'tslint.json',
force: false
configFile: 'eslintrc.js'
},
files: {
src: [
......@@ -575,7 +574,7 @@ module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-postcss');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-exec');
grunt.loadNpmTasks('grunt-tslint');
grunt.loadNpmTasks('grunt-eslint');
grunt.loadNpmTasks('grunt-stylelint');
grunt.loadNpmTasks('grunt-lintspaces');
grunt.loadNpmTasks('grunt-contrib-imagemin');
......@@ -595,11 +594,11 @@ module.exports = function (grunt) {
* call "$ grunt lint"
*
* this task does the following things:
* - tslint
* - eslint
* - stylelint
* - lintspaces
*/
grunt.registerTask('lint', ['tslint', 'stylelint', 'lintspaces']);
grunt.registerTask('lint', ['eslint', 'stylelint', 'lintspaces']);
/**
* grunt format
......@@ -641,11 +640,11 @@ module.exports = function (grunt) {
* call "$ grunt scripts"
*
* this task does the following things:
* - 1) Check all TypeScript files (*.ts) with TSLint which are located in sysext/<EXTKEY>/Resources/Private/TypeScript/*.ts
* - 1) Check all TypeScript files (*.ts) with ESLint which are located in sysext/<EXTKEY>/Resources/Private/TypeScript/*.ts
* - 2) Compiles all TypeScript files (*.ts) which are located in sysext/<EXTKEY>/Resources/Private/TypeScript/*.ts
* - 3) Copy all generated JavaScript and Map files to public folders
*/
grunt.registerTask('scripts', ['tsconfig', 'tslint', 'tsclean', 'exec:ts', 'copy:ts_files', 'terser:typescript']);
grunt.registerTask('scripts', ['tsconfig', 'eslint', 'tsclean', 'exec:ts', 'copy:ts_files', 'terser:typescript']);
/**
* grunt tsclean task
......
......@@ -55,7 +55,7 @@ namespace TYPO3 {
},
);
this.contentSettings = this.querySelectorAll(AdminPanelSelectors.contentSettingsTriggerRole).map(
(contentSettingTrigger: HTMLElement) => {
(contentSettingTrigger: HTMLElement) => {
const contentSettingElement = contentSettingTrigger
.closest(AdminPanelSelectors.contentParentClass)
.querySelector(AdminPanelSelectors.contentSettingsParentClass);
......@@ -193,16 +193,16 @@ namespace TYPO3 {
private addBackdropListener(): void {
this.querySelectorAll('.' + AdminPanelClasses.backdrop)
.forEach((elm: HTMLElement) => {
elm.addEventListener('click', () => {
this.removeBackdrop();
this
.querySelectorAll(AdminPanelSelectors.moduleTriggerRole)
.forEach((innerElm: HTMLElement) => {
innerElm.closest(AdminPanelSelectors.moduleParentClass)
.classList.remove(AdminPanelClasses.activeModule);
});
elm.addEventListener('click', () => {
this.removeBackdrop();
this
.querySelectorAll(AdminPanelSelectors.moduleTriggerRole)
.forEach((innerElm: HTMLElement) => {
innerElm.closest(AdminPanelSelectors.moduleParentClass)
.classList.remove(AdminPanelClasses.activeModule);
});
});
});
});
}
}
......@@ -338,7 +338,7 @@ namespace TYPO3 {
}
private initializeEvents(): void {
this.trigger.addEventListener('click', (event: MouseEvent) => {
this.trigger.addEventListener('click', () => {
this.adminPanel.removeBackdrop();
if (this.isActive()) {
this.disable();
......
......@@ -19,7 +19,7 @@ import {AbstractAction} from './AbstractAction';
class ImmediateAction extends AbstractAction {
protected callback: () => void;
public execute(el: HTMLElement): Promise<any> {
public execute(): Promise<any> {
return this.executeCallback();
}
......
......@@ -51,11 +51,7 @@ class ContextMenuActions {
);
}
/**
* @param {string} table
* @param {number} uid
*/
public static viewRecord(table: string, uid: number): void {
public static viewRecord(): void {
const $viewUrl = $(this).data('preview-url');
if ($viewUrl) {
const previewWin = window.open($viewUrl, 'newTYPO3frontendWindow');
......@@ -91,11 +87,7 @@ class ContextMenuActions {
);
}
/**
* @param {string} table
* @param {number} uid
*/
public static newContentWizard(table: string, uid: number): void {
public static newContentWizard(): void {
const $me = $(this);
let $wizardUrl = $me.data('new-wizard-url');
if ($wizardUrl) {
......@@ -139,22 +131,14 @@ class ContextMenuActions {
ModuleMenu.App.showModule('web_list', 'id=' + pageId);
}
/**
* @param {string} table
* @param {number} uid
*/
public static pagesSort(table: string, uid: number): void {
public static pagesSort(): void {
const pagesSortUrl = $(this).data('pages-sort-url');
if (pagesSortUrl) {
Viewport.ContentContainer.setUrl(pagesSortUrl);
}
}
/**
* @param {string} table
* @param {number} uid
*/
public static pagesNewMultiple(table: string, uid: number): void {
public static pagesNewMultiple(): void {
const pagesSortUrl = $(this).data('pages-new-multiple-url');
if (pagesSortUrl) {
Viewport.ContentContainer.setUrl(pagesSortUrl);
......@@ -305,7 +289,7 @@ class ContextMenuActions {
},
error: (): void => {
Notification.error(
'Clearing page caches went wrong on the server side.',
'Clearing page caches went wrong on the server side.',
);
},
});
......
......@@ -18,12 +18,6 @@ class DocumentSaveActions {
private static instance: DocumentSaveActions = null;
private preSubmitCallbacks: Array<Function> = [];
private constructor() {
$((): void => {
this.initializeSaveHandling();
});
}
public static getInstance(): DocumentSaveActions {
if (DocumentSaveActions.instance === null) {
DocumentSaveActions.instance = new DocumentSaveActions();
......@@ -32,6 +26,12 @@ class DocumentSaveActions {
return DocumentSaveActions.instance;
}
private constructor() {
$((): void => {
this.initializeSaveHandling();
});
}
/**
* Adds a callback being executed before submit
*
......
......@@ -68,6 +68,12 @@ interface DragUploaderOptions {
outputColor?: string;
}
interface FileConflict {
original: UploadedFile;
uploaded: InternalFile;
action: Action;
}
class DragUploaderPlugin {
public irreObjectUid: number;
public $fileList: JQuery;
......@@ -81,17 +87,17 @@ class DragUploaderPlugin {
/**
* Array of files which are asked for being overridden
*/
private askForOverride: Array<{ original: UploadedFile, uploaded: InternalFile, action: Action }> = [];
private askForOverride: Array<FileConflict> = [];
private percentagePerFile: number = 1;
private $body: JQuery;
private $element: JQuery;
private $dropzone: JQuery;
private $dropzoneMask: JQuery;
private fileInput: HTMLInputElement;
private readonly $element: JQuery;
private readonly $dropzone: JQuery;
private readonly $dropzoneMask: JQuery;
private readonly fileInput: HTMLInputElement;
private browserCapabilities: { fileReader: boolean; DnD: boolean; Progress: boolean };
private dropZoneInsertBefore: boolean;
private readonly dropZoneInsertBefore: boolean;
private queueLength: number;
private defaultAction: string;
......@@ -256,7 +262,7 @@ class DragUploaderPlugin {
// Check for each file if is already exist before adding it to the queue
const ajaxCalls: JQueryXHR[] = [];
$.each(files, (i: string, file) => {
$.each(files, (i: string, file: InternalFile) => {
ajaxCalls[parseInt(i, 10)] = $.ajax({
url: TYPO3.settings.ajaxUrls.file_exists,
data: {
......@@ -274,8 +280,7 @@ class DragUploaderPlugin {
});
NProgress.inc(this.percentagePerFile);
} else {
// Unused var _ is necessary as "no-unused-expression" is active
const _ = new FileQueueItem(this, file, Action.SKIP);
new FileQueueItem(this, file, Action.SKIP);
}
},
});
......@@ -320,7 +325,7 @@ class DragUploaderPlugin {
$.ajax({
url: TYPO3.settings.ajaxUrls.flashmessages_render,
cache: false,
success: (data) => {
success: (data: any) => {
$.each(data, (index: number, flashMessage: { title: string, message: string, severity: number }) => {
Notification.showMessage(flashMessage.title, flashMessage.message, flashMessage.severity);
});
......@@ -355,8 +360,8 @@ class DragUploaderPlugin {
const $record = $('<tr />').append(
$('<td />').append(
(this.askForOverride[i].original.thumbUrl !== ''
? $('<img />', {src: this.askForOverride[i].original.thumbUrl, height: 40})
: $(this.askForOverride[i].original.icon)
? $('<img />', {src: this.askForOverride[i].original.thumbUrl, height: 40})
: $(this.askForOverride[i].original.icon)
),
),
$('<td />').html(
......@@ -429,7 +434,7 @@ class DragUploaderPlugin {
if (value !== '') {
// mass action was selected, apply action to every file
$modal.find('.t3js-actions').each((i, select) => {
$modal.find('.t3js-actions').each((i: number, select: HTMLSelectElement) => {
const $select = $(select),
index = parseInt($select.data('override'), 10);
$select.val(value).prop('disabled', 'disabled');
......@@ -447,15 +452,14 @@ class DragUploaderPlugin {
uploader.askForOverride = [];
Modal.dismiss();
} else if ((<HTMLInputElement>(e.target)).name === 'continue') {
$.each(uploader.askForOverride, (key, fileInfo) => {
$.each(uploader.askForOverride, (key: number, fileInfo: FileConflict) => {
if (fileInfo.action === Action.USE_EXISTING) {
DragUploader.addFileToIrre(
uploader.irreObjectUid,
fileInfo.original,
);
} else if (fileInfo.action !== Action.SKIP) {
// Unused var _ is necessary as "no-unused-expression" is active
const _ = new FileQueueItem(uploader, fileInfo.uploaded, fileInfo.action);
new FileQueueItem(uploader, fileInfo.uploaded, fileInfo.action);
}
});
uploader.askForOverride = [];
......@@ -740,7 +744,6 @@ class DragUploader {
$('.t3js-drag-uploader').dragUploader(opts);
});
}
}
/**
......@@ -767,7 +770,7 @@ export const initialize = function (): void {
&& 'undefined' !== typeof TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/DragUploader']
) {
$.each(
TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/DragUploader'], (pos, moduleName) => {
TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/DragUploader'], (pos: number, moduleName: string) => {
require([moduleName]);
},
);
......
......@@ -129,8 +129,8 @@ class InlineControlContainer {
private static registerInfoButton(e: Event): void {
let target: HTMLElement;
if ((target = InlineControlContainer.getDelegatedEventTarget(
e.target,
Selectors.infoWindowButton)
e.target,
Selectors.infoWindowButton)
) === null) {
return;
}
......@@ -186,7 +186,7 @@ class InlineControlContainer {
* @param {UniqueDefinitionCollection} hashmap
*/
private static getValuesFromHashMap(hashmap: UniqueDefinitionCollection): Array<any> {
return Object.keys(hashmap).map(key => hashmap[key]);
return Object.keys(hashmap).map((key: string) => hashmap[key]);
}
private static selectOptionValueExists(selectElement: HTMLSelectElement, value: string): boolean {
......@@ -374,7 +374,7 @@ class InlineControlContainer {
*/
private handlePostMessage = (e: MessageEvent): void => {
if (!MessageUtility.verifyOrigin(e.origin)) {
throw 'Denied message sent by ' + e.origin;
throw 'Denied message sent by ' + e.origin;
}
if (typeof e.data.objectGroup === 'undefined') {
......@@ -448,8 +448,8 @@ class InlineControlContainer {
private registerEnableDisableButton(e: Event): void {
let target: HTMLElement;
if ((target = InlineControlContainer.getDelegatedEventTarget(
e.target,
Selectors.enableDisableRecordButtonSelector)
e.target,
Selectors.enableDisableRecordButtonSelector)
) === null) {
return;
}
......@@ -492,8 +492,8 @@ class InlineControlContainer {
private registerDeleteButton(e: Event): void {
let target: HTMLElement;
if ((target = InlineControlContainer.getDelegatedEventTarget(
e.target,
Selectors.deleteRecordButtonSelector)
e.target,
Selectors.deleteRecordButtonSelector)
) === null) {
return;
}
......
......@@ -103,10 +103,10 @@ export abstract class AbstractSortableSelectItems {
return;
}
aside.addEventListener('click', (e): void => {
aside.addEventListener('click', (e: Event): void => {
let target: HTMLAnchorElement;
if ((target = <HTMLAnchorElement>(<Element>e.target).closest('.t3js-btn-option')) === null) {
if ((target = (<Element>e.target).closest('.t3js-btn-option')) === null) {
if ((<Element>e.target).matches('.t3js-btn-option')) {
target = <HTMLAnchorElement>e.target;
}
......
......@@ -65,7 +65,7 @@ class SelectBoxFilter {
this.selectElement.innerHTML = '';
const matchFilter = new RegExp(filterText, 'i');
this.$availableOptions.each((i, el): void => {
this.$availableOptions.each((i: number, el: HTMLElement): void => {
if (filterText.length === 0 || el.textContent.match(matchFilter)) {
this.selectElement.appendChild(el);
}
......
......@@ -83,7 +83,7 @@ class SlugElement {
}
private registerEvents(): void {
const fieldsToListenOnList = Object.keys(this.getAvailableFieldsForProposalGeneration()).map(k => this.fieldsToListenOn[k]);
const fieldsToListenOnList = Object.keys(this.getAvailableFieldsForProposalGeneration()).map((k: string) => this.fieldsToListenOn[k]);
// Listen on 'listenerFieldNames' for new pages. This is typically the 'title' field
// of a page to create slugs from the title when title is set / changed.
......@@ -97,7 +97,7 @@ class SlugElement {
}
// Clicking the recreate button makes new slug proposal created from 'title' field
$(this.$fullElement).on('click', Selectors.recreateButton, (e): void => {
$(this.$fullElement).on('click', Selectors.recreateButton, (e: JQueryEventObject): void => {
e.preventDefault();
if (this.$readOnlyField.hasClass('hidden')) {
// Switch to readonly version - similar to 'new' page where field is
......@@ -122,7 +122,7 @@ class SlugElement {
// Clicking the toggle button toggles the read only field and the input field.
// Also set the value of either the read only or the input field to the hidden field
// and update the value of the read only field after manual change of the input field.
$(this.$fullElement).on('click', Selectors.toggleButton, (e): void => {
$(this.$fullElement).on('click', Selectors.toggleButton, (e: JQueryEventObject): void => {
e.preventDefault();
const showReadOnlyField = this.$readOnlyField.hasClass('hidden');
this.$readOnlyField.toggleClass('hidden', !showReadOnlyField);
......
......@@ -86,7 +86,7 @@ export class AjaxDispatcher {
// TODO: This is subject to be removed
if (json.scriptCall && json.scriptCall.length > 0) {
$.each(json.scriptCall, (index: number, value: string): void => {
// tslint:disable-next-line:no-eval
// eslint-disable-next-line no-eval
eval(value);
});
}
......
......@@ -111,7 +111,7 @@ class FlexFormElement {
this.createSortable();
// allow delete of a single section
this.$el.off('click').on('click', this.opts.deleteIconSelector, (evt) => {
this.$el.off('click').on('click', this.opts.deleteIconSelector, (evt: JQueryEventObject) => {
evt.preventDefault();
const confirmTitle = TYPO3.lang['flexform.section.delete.title'] || 'Are you sure?';
......@@ -129,7 +129,7 @@ class FlexFormElement {
});
// allow the toggle open/close of the main selection
this.$el.on('click', this.opts.sectionToggleButtonSelector, (evt) => {
this.$el.on('click', this.opts.sectionToggleButtonSelector, (evt: JQueryEventObject) => {
evt.preventDefault();
const $sectionEl = $(evt.currentTarget).closest(this.opts.sectionSelector);
this.toggleSection($sectionEl);
......@@ -171,25 +171,25 @@ class FlexFormElement {
// hides the flexform section and shows a preview text
// or shows the form parts
private toggleSection($sectionEl: JQuery): void {
const $contentEl = $sectionEl.find(this.opts.sectionContentSelector);
// display/hide the content of this flexform section
$contentEl.toggle();
if ($contentEl.is(':visible')) {
// show the open icon, and set the hidden field for toggling to "hidden"
$sectionEl.find(this.opts.sectionToggleIconOpenSelector).show();
$sectionEl.find(this.opts.sectionToggleIconCloseSelector).hide();
$sectionEl.find(this.opts.sectionToggleInputFieldSelector).val(0);
} else {
// show the close icon, and set the hidden field for toggling to "1"
$sectionEl.find(this.opts.sectionToggleIconOpenSelector).hide();
$sectionEl.find(this.opts.sectionToggleIconCloseSelector).show();
$sectionEl.find(this.opts.sectionToggleInputFieldSelector).val(1);
}
// see if the preview content needs to be generated
this.generateSectionPreview($sectionEl);
const $contentEl = $sectionEl.find(this.opts.sectionContentSelector);
// display/hide the content of this flexform section
$contentEl.toggle();
if ($contentEl.is(':visible')) {
// show the open icon, and set the hidden field for toggling to "hidden"
$sectionEl.find(this.opts.sectionToggleIconOpenSelector).show();
$sectionEl.find(this.opts.sectionToggleIconCloseSelector).hide();
$sectionEl.find(this.opts.sectionToggleInputFieldSelector).val(0);
} else {
// show the close icon, and set the hidden field for toggling to "1"
$sectionEl.find(this.opts.sectionToggleIconOpenSelector).hide();
$sectionEl.find(this.opts.sectionToggleIconCloseSelector).show();
$sectionEl.find(this.opts.sectionToggleInputFieldSelector).val(1);
}
// see if the preview content needs to be generated
this.generateSectionPreview($sectionEl);
}
// function to generate the section preview in the header
......@@ -225,7 +225,7 @@ class FlexFormElement {
$.fn.t3FormEngineFlexFormElement = function(options: FlexFormElementOptions): JQuery {
// apply all util functions to ourself (for use in templates, etc.)
return this.each(function(this: HTMLElement): void {
const _ = new FlexFormElement(this, options);
new FlexFormElement(this, options);
});
};
......@@ -259,7 +259,7 @@ $(function(): void {
$('.t3-flex-container').t3FormEngineFlexFormElement();
if (response.scriptCall && response.scriptCall.length > 0) {
$.each(response.scriptCall, function(index: number, value: string): void {
/* tslint:disable-next-line:no-eval */
// eslint-disable-next-line no-eval
eval(value);
});
}
......
......@@ -565,9 +565,9 @@ export class GridEditor {
+ '<br />'
+ TYPO3.lang.grid_column + ': '
+ (typeof cell.column === 'undefined' || isNaN(cell.column)
? TYPO3.lang.grid_notSet
: parseInt(cell.column, 10)
),
? TYPO3.lang.grid_notSet
: parseInt(cell.column, 10)
),
),
);
if (cell.colspan > 1) {
......
......@@ -55,11 +55,13 @@ class Icons {
* @param {MarkupIdentifiers} markupIdentifier
* @returns {JQueryPromise<any>}
*/
public getIcon(identifier: string,
size: Sizes,
overlayIdentifier?: string,
state?: string,
markupIdentifier?: MarkupIdentifiers): JQueryPromise<any> {
public getIcon(
identifier: string,
size: Sizes,
overlayIdentifier?: string,
state?: string,
markupIdentifier?: MarkupIdentifiers,
): JQueryPromise<any> {
/**
* Icon keys:
......
......@@ -276,7 +276,7 @@ class ImageManipulation {
title: modalTitle,
});
this.currentModal.on('hide.bs.modal', (e: JQueryEventObject): void => {
this.currentModal.on('hide.bs.modal', (): void => {
this.destroy();
});
// do not dismiss the modal when clicking beside it to avoid data loss
......@@ -570,98 +570,98 @@ class ImageManipulation {
this.focusArea = $('<div id="t3js-cropper-focus-area" class="cropper-focus-area"></div>');
container.append(this.focusArea);
this.focusArea
.draggable({
containment: container,
create: (): void => {
this.scaleAndMoveFocusArea(this.currentCropVariant.focusArea);
},
drag: (): void => {
const {left, top}: Offset = container.offset();
const {left: fLeft, top: fTop}: Offset = this.focusArea.offset();
const {focusArea, coverAreas}: {focusArea?: Area, coverAreas?: Area[]} = this.currentCropVariant;
focusArea.x = (fLeft - left) / container.width();
focusArea.y = (fTop - top) / container.height();
this.updatePreviewThumbnail(this.currentCropVariant</