Merge pull request #15248 from overleaf/mj-eslint-angular-components

[web] Add eslint rules for angularjs components

GitOrigin-RevId: 1343d584368faeb912f04c5879228bcbd07a042a
This commit is contained in:
Mathias Jakobsen 2023-10-17 09:36:46 +01:00 committed by Copybot
parent 4b6b9c3bef
commit 1a92e1b664
33 changed files with 1846 additions and 1719 deletions

View file

@ -226,7 +226,32 @@
// Require .jsx or .tsx file extension when using JSX
"react/jsx-filename-extension": ["error", {
"extensions": [".jsx", ".tsx"]
}]
}],
"no-restricted-syntax": [
"error",
// Begin: Make sure angular can withstand minification
{
"selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > :function[params.length > 0]",
"message": "Wrap the function in an array with the parameter names, to withstand minifcation. E.g. App.controller('MyController', ['param1', function(param1) {}]"
},
{
"selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrayExpression > ArrowFunctionExpression",
"message": "Use standard function syntax instead of arrow function syntax in angular components. E.g. function(param1) {}"
},
{
"selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrowFunctionExpression",
"message": "Use standard function syntax instead of arrow function syntax in angular components. E.g. function(param1) {}"
},
{
"selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrayExpression > :not(:function, Identifier):last-child",
"message": "Last element of the array must be a function. E.g ['param1', function(param1) {}]"
},
{
"selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrayExpression[elements.length=0]",
"message": "Array must not be empty. Add parameters and a function. E.g ['param1', function(param1) {}]"
}
// End: Make sure angular can withstand minification
]
}
},
// React + TypeScript-specific rules

View file

@ -4,7 +4,8 @@ App.directive('asyncForm', [
'$http',
'validateCaptcha',
'validateCaptchaV3',
($http, validateCaptcha, validateCaptchaV3) => ({
function ($http, validateCaptcha, validateCaptchaV3) {
return {
controller: [
'$scope',
'$location',
@ -169,10 +170,12 @@ App.directive('asyncForm', [
submit()
}
},
}),
}
},
])
App.directive('formMessages', () => ({
App.directive('formMessages', function () {
return {
restrict: 'E',
template: `\
<div class="alert" ng-class="{
@ -186,4 +189,5 @@ App.directive('formMessages', () => ({
scope: {
form: '=for',
},
}))
}
})

View file

@ -2,7 +2,8 @@ import _ from 'lodash'
import App from '../base'
App.directive('bookmarkableTabset', [
'$location',
$location => ({
function ($location) {
return {
restrict: 'A',
require: 'tabset',
link(scope, el, attrs, tabset) {
@ -29,7 +30,10 @@ App.directive('bookmarkableTabset', [
// within a tab to another tab
const linksToTabs = document.querySelectorAll('.link-to-tab')
const _clickLinkToTab = event => {
const hash = event.currentTarget.getAttribute('href').split('#').pop()
const hash = event.currentTarget
.getAttribute('href')
.split('#')
.pop()
_makeActive(hash)
}
@ -40,12 +44,14 @@ App.directive('bookmarkableTabset', [
}
})
},
}),
}
},
])
App.directive('bookmarkableTab', [
'$location',
$location => ({
function ($location) {
return {
restrict: 'A',
require: 'tab',
link(scope, el, attrs, tab) {
@ -60,5 +66,6 @@ App.directive('bookmarkableTab', [
})
}
},
}),
}
},
])

View file

@ -6,7 +6,8 @@ import _ from 'lodash'
*/
import App from '../base'
import '../vendor/libs/passfield'
App.directive('complexPassword', () => ({
App.directive('complexPassword', function () {
return {
require: ['^asyncForm', 'ngModel'],
link(scope, element, attrs, ctrl) {
@ -61,7 +62,8 @@ App.directive('complexPassword', () => ({
const email = asyncFormCtrl.getEmail() || window.usersEmail
if (!isValid) {
scope.complexPasswordErrorMessage = passField.getPassValidationMessage()
scope.complexPasswordErrorMessage =
passField.getPassValidationMessage()
} else if (typeof email === 'string' && email !== '') {
const startOfEmail = email.split('@')[0]
if (
@ -87,4 +89,5 @@ App.directive('complexPassword', () => ({
return modelValue
})
},
}))
}
})

View file

@ -7,7 +7,8 @@
*/
import App from '../base'
export default App.directive('equals', () => ({
export default App.directive('equals', function () {
return {
require: 'ngModel',
link(scope, elem, attrs, ctrl) {
const firstField = `#${attrs.equals}`
@ -18,4 +19,5 @@ export default App.directive('equals', () => ({
})
)
},
}))
}
})

View file

@ -63,7 +63,8 @@ const isInViewport = function (element) {
export default App.directive('eventTracking', [
'eventTracking',
eventTracking => ({
function (eventTracking) {
return {
scope: {
eventTracking: '@',
eventSegmentation: '=?',
@ -74,7 +75,9 @@ export default App.directive('eventTracking', [
const sendMBFunction = attrs.eventTrackingSendOnce
? 'sendMBOnce'
: 'sendMB'
const sendGAFunction = attrs.eventTrackingSendOnce ? 'sendGAOnce' : 'send'
const sendGAFunction = attrs.eventTrackingSendOnce
? 'sendGAOnce'
: 'send'
const segmentation = scope.eventSegmentation || {}
segmentation.page = window.location.pathname
@ -126,5 +129,6 @@ export default App.directive('eventTracking', [
)
}
},
}),
}
},
])

View file

@ -7,7 +7,8 @@
*/
import App from '../base'
export default App.directive('expandableTextArea', () => ({
export default App.directive('expandableTextArea', function () {
return {
restrict: 'A',
link(scope, el) {
const resetHeight = function () {
@ -26,4 +27,5 @@ export default App.directive('expandableTextArea', () => ({
return scope.$watch(() => el.val(), resetHeight)
},
}))
}
})

View file

@ -12,7 +12,8 @@
import App from '../base'
App.directive('focusWhen', [
'$timeout',
$timeout => ({
function ($timeout) {
return {
restrict: 'A',
link(scope, element, attr) {
return scope.$watch(attr.focusWhen, function (value) {
@ -21,19 +22,23 @@ App.directive('focusWhen', [
}
})
},
}),
}
},
])
App.directive('focusOn', () => ({
App.directive('focusOn', function () {
return {
restrict: 'A',
link(scope, element, attrs) {
return scope.$on(attrs.focusOn, () => element.focus())
},
}))
}
})
App.directive('selectWhen', [
'$timeout',
$timeout => ({
function ($timeout) {
return {
restrict: 'A',
link(scope, element, attr) {
return scope.$watch(attr.selectWhen, function (value) {
@ -42,19 +47,23 @@ App.directive('selectWhen', [
}
})
},
}),
}
},
])
App.directive('selectOn', () => ({
App.directive('selectOn', function () {
return {
restrict: 'A',
link(scope, element, attrs) {
return scope.$on(attrs.selectOn, () => element.select())
},
}))
}
})
App.directive('selectNameWhen', [
'$timeout',
$timeout => ({
function ($timeout) {
return {
restrict: 'A',
link(scope, element, attrs) {
return scope.$watch(attrs.selectNameWhen, function (value) {
@ -63,19 +72,23 @@ App.directive('selectNameWhen', [
}
})
},
}),
}
},
])
App.directive('selectNameOn', () => ({
App.directive('selectNameOn', function () {
return {
restrict: 'A',
link(scope, element, attrs) {
return scope.$on(attrs.selectNameOn, () => selectName(element))
},
}))
}
})
App.directive('focus', [
'$timeout',
$timeout => ({
function ($timeout) {
return {
scope: {
trigger: '@focus',
},
@ -87,7 +100,8 @@ App.directive('focus', [
}
})
},
}),
}
},
])
function selectName(element) {

View file

@ -8,7 +8,8 @@
*/
import App from '../base'
export default App.directive('maxHeight', () => ({
export default App.directive('maxHeight', function () {
return {
restrict: 'A',
link(scope, element, attrs) {
return scope.$watch(attrs.maxHeight, function (value) {
@ -17,4 +18,5 @@ export default App.directive('maxHeight', () => ({
}
})
},
}))
}
})

View file

@ -7,13 +7,12 @@
*/
import App from '../base'
export default App.directive(
'onEnter',
() => (scope, element, attrs) =>
export default App.directive('onEnter', function () {
return (scope, element, attrs) =>
element.bind('keydown keypress', function (event) {
if (event.which === 13) {
scope.$apply(() => scope.$eval(attrs.onEnter, { event }))
return event.preventDefault()
}
})
)
})

View file

@ -7,7 +7,8 @@
*/
import App from '../base'
export default App.directive('rightClick', () => ({
export default App.directive('rightClick', function () {
return {
restrict: 'A',
link(scope, element, attrs) {
return element.bind('contextmenu', function (e) {
@ -16,4 +17,5 @@ export default App.directive('rightClick', () => ({
return scope.$eval(attrs.rightClick)
})
},
}))
}
})

View file

@ -13,7 +13,8 @@ import App from '../base'
export default App.directive('updateScrollBottomOn', [
'$timeout',
$timeout => ({
function ($timeout) {
return {
restrict: 'A',
link(scope, element, attrs, ctrls) {
// We keep the offset from the bottom fixed whenever the event fires
@ -55,5 +56,6 @@ export default App.directive('updateScrollBottomOn', [
)
)
},
}),
}
},
])

View file

@ -11,7 +11,8 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
import App from '../base'
App.directive('selectAllList', () => ({
App.directive('selectAllList', function () {
return {
controller: [
'$scope',
function ($scope) {
@ -24,9 +25,11 @@ App.directive('selectAllList', () => ({
},
],
link(scope, element, attrs) {},
}))
}
})
App.directive('selectAll', () => ({
App.directive('selectAll', function () {
return {
require: '^selectAllList',
link(scope, element, attrs, selectAllListController) {
scope.$on('select-all:clear', () => element.prop('checked', false))
@ -40,9 +43,11 @@ App.directive('selectAll', () => ({
return true
})
},
}))
}
})
App.directive('selectIndividual', () => ({
App.directive('selectIndividual', function () {
return {
require: '^selectAllList',
scope: {
ngModel: '=',
@ -88,11 +93,16 @@ App.directive('selectIndividual', () => ({
return (ignoreChanges = false)
})
},
}))
}
})
export default App.directive('selectRow', () => ({
export default App.directive('selectRow', function () {
return {
scope: true,
link(scope, element, attrs) {
return element.on('click', e => scope.$broadcast('select-all:row-clicked'))
return element.on('click', e =>
scope.$broadcast('select-all:row-clicked')
)
},
}))
}
})

View file

@ -9,16 +9,20 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
import App from '../base'
App.directive('stopPropagation', () => ({
App.directive('stopPropagation', function () {
return {
restrict: 'A',
link(scope, element, attrs) {
return element.bind(attrs.stopPropagation, e => e.stopPropagation())
},
}))
}
})
export default App.directive('preventDefault', () => ({
export default App.directive('preventDefault', function () {
return {
restrict: 'A',
link(scope, element, attrs) {
return element.bind(attrs.preventDefault, e => e.preventDefault())
},
}))
}
})

View file

@ -12,7 +12,8 @@ import App from '../base'
export default App.directive('videoPlayState', [
'$parse',
$parse => ({
function ($parse) {
return {
restrict: 'A',
link(scope, element, attrs) {
const videoDOMEl = element[0]
@ -28,5 +29,6 @@ export default App.directive('videoPlayState', [
}
)
},
}),
}
},
])

View file

@ -21,7 +21,8 @@ export default App.directive('layout', [
'$parse',
'$compile',
'ide',
($parse, $compile, ide) => ({
function ($parse, $compile, ide) {
return {
compile() {
return {
pre(scope, element, attrs) {
@ -203,7 +204,8 @@ export default App.directive('layout', [
}
customTogglerScope.tooltipMsgWhenOpen = customTogglerMsgWhenOpen
customTogglerScope.tooltipMsgWhenClosed = customTogglerMsgWhenClosed
customTogglerScope.tooltipMsgWhenClosed =
customTogglerMsgWhenClosed
customTogglerScope.tooltipPlacement =
customTogglerPane === 'east' ? 'left' : 'right'
@ -316,5 +318,6 @@ aria-label=\"{{ isOpen ? tooltipMsgWhenOpen : tooltipMsgWhenClosed }}\">\
},
}
},
}),
}
},
])

View file

@ -12,10 +12,12 @@
import App from '../../base'
import SafePath from './SafePath'
export default App.directive('validFile', () => ({
export default App.directive('validFile', function () {
return {
require: 'ngModel',
link(scope, element, attrs, ngModelCtrl) {
return (ngModelCtrl.$validators.validFile = filename =>
SafePath.isCleanFilename(filename))
},
}))
}
})

View file

@ -3,7 +3,8 @@ import App from '../../base'
export default App.directive('verticalResizablePanes', [
'localStorage',
'ide',
(localStorage, ide) => ({
function (localStorage, ide) {
return {
restrict: 'A',
link(scope, element, attrs) {
const name = attrs.verticalResizablePanes
@ -44,7 +45,8 @@ export default App.directive('verticalResizablePanes', [
},
}
const toggledExternally = attrs.verticalResizablePanesToggledExternallyOn
const toggledExternally =
attrs.verticalResizablePanesToggledExternallyOn
const hiddenExternally = attrs.verticalResizablePanesHiddenExternallyOn
const hiddenInitially = attrs.verticalResizablePanesHiddenInitially
const resizeOn = attrs.verticalResizablePanesResizeOn
@ -77,7 +79,10 @@ export default App.directive('verticalResizablePanes', [
scope.$on(toggledExternally, (e, open) => {
if (open) {
enableResizer()
layoutHandle.sizePane('south', storedSize ?? defaultSize ?? 'auto')
layoutHandle.sizePane(
'south',
storedSize ?? defaultSize ?? 'auto'
)
} else {
disableResizer()
layoutHandle.sizePane('south', 'auto')
@ -129,5 +134,6 @@ export default App.directive('verticalResizablePanes', [
layoutHandle.hide('south')
}
},
}),
}
},
])

View file

@ -1,6 +1,7 @@
import App from '../../../base'
export default App.directive('formattingButtons', () => ({
export default App.directive('formattingButtons', function () {
return {
scope: {
buttons: '=',
opening: '=',
@ -14,4 +15,5 @@ export default App.directive('formattingButtons', () => ({
},
templateUrl: 'formattingButtonsTpl',
}))
}
})

View file

@ -1,6 +1,7 @@
import App from '../../../base'
export default App.directive('toggleSwitch', () => ({
export default App.directive('toggleSwitch', function () {
return {
restrict: 'E',
scope: {
description: '@',
@ -33,4 +34,5 @@ export default App.directive('toggleSwitch', () => ({
<label for="toggle-switch-true-{{$id}}" class="toggle-switch-label"><span>{{labelTrue}}</span></label>
</fieldset>\
`,
}))
}
})

View file

@ -1,6 +1,7 @@
import App from '../../../base'
export default App.directive('historyDraggableBoundary', () => ({
export default App.directive('historyDraggableBoundary', function () {
return {
scope: {
historyDraggableBoundary: '@',
historyDraggableBoundaryOnDragStart: '&',
@ -30,4 +31,5 @@ export default App.directive('historyDraggableBoundary', () => ({
},
})
},
}))
}
})

View file

@ -1,6 +1,7 @@
import App from '../../../base'
export default App.directive('historyDroppableArea', () => ({
export default App.directive('historyDroppableArea', function () {
return {
scope: {
historyDroppableAreaOnDrop: '&',
historyDroppableAreaOnOver: '&',
@ -11,14 +12,17 @@ export default App.directive('historyDroppableArea', () => ({
element.droppable({
accept: e => '.history-entry-toV-handle, .history-entry-fromV-handle',
drop: (e, ui) => {
const draggedBoundary = ui.draggable.data('selectionBoundary').boundary
const draggedBoundary =
ui.draggable.data('selectionBoundary').boundary
ui.helper.data('wasProperlyDropped', true)
scope.historyDroppableAreaOnDrop({ boundary: draggedBoundary })
},
over: (e, ui) => {
const draggedBoundary = ui.draggable.data('selectionBoundary').boundary
const draggedBoundary =
ui.draggable.data('selectionBoundary').boundary
scope.historyDroppableAreaOnOver({ boundary: draggedBoundary })
},
})
},
}))
}
})

View file

@ -11,7 +11,8 @@
*/
import App from '../../../base'
export default App.directive('infiniteScroll', () => ({
export default App.directive('infiniteScroll', function () {
return {
link(scope, element, attrs, ctrl) {
const innerElement = element.find('.infinite-scroll-inner')
element.css({ 'overflow-y': 'auto' })
@ -50,4 +51,5 @@ export default App.directive('infiniteScroll', () => ({
}
})
},
}))
}
})

View file

@ -1,6 +1,7 @@
import App from '../../../base'
let content = ''
App.directive('addCommentEntry', () => ({
App.directive('addCommentEntry', function () {
return {
restrict: 'E',
templateUrl: 'addCommentEntryTemplate',
scope: {
@ -57,4 +58,5 @@ App.directive('addCommentEntry', () => ({
scope.state.content = ''
}
},
}))
}
})

View file

@ -13,7 +13,8 @@ import App from '../../../base'
export default App.directive('aggregateChangeEntry', [
'$timeout',
$timeout => ({
function ($timeout) {
return {
restrict: 'E',
templateUrl: 'aggregateChangeEntryTemplate',
scope: {
@ -68,5 +69,6 @@ export default App.directive('aggregateChangeEntry', [
insertionContentLength > scope.contentLimit)
)
},
}),
}
},
])

View file

@ -10,7 +10,8 @@
*/
import App from '../../../base'
export default App.directive('bulkActionsEntry', () => ({
export default App.directive('bulkActionsEntry', function () {
return {
restrict: 'E',
templateUrl: 'bulkActionsEntryTemplate',
scope: {
@ -22,4 +23,5 @@ export default App.directive('bulkActionsEntry', () => ({
scope.bulkAccept = () => scope.onBulkAccept()
return (scope.bulkReject = () => scope.onBulkReject())
},
}))
}
})

View file

@ -13,7 +13,8 @@ import App from '../../../base'
export default App.directive('changeEntry', [
'$timeout',
$timeout => ({
function ($timeout) {
return {
restrict: 'E',
templateUrl: 'changeEntryTemplate',
scope: {
@ -53,5 +54,6 @@ export default App.directive('changeEntry', [
(scope.needsCollapsing = contentLength > scope.contentLimit)
)
},
}),
}
},
])

View file

@ -13,7 +13,8 @@ import App from '../../../base'
export default App.directive('commentEntry', [
'$timeout',
$timeout => ({
function ($timeout) {
return {
restrict: 'E',
templateUrl: 'commentEntryTemplate',
scope: {
@ -91,5 +92,6 @@ export default App.directive('commentEntry', [
}
})
},
}),
}
},
])

View file

@ -11,7 +11,8 @@
*/
import App from '../../../base'
export default App.directive('resolvedCommentEntry', () => ({
export default App.directive('resolvedCommentEntry', function () {
return {
restrict: 'E',
templateUrl: 'resolvedCommentEntryTemplate',
scope: {
@ -33,4 +34,5 @@ export default App.directive('resolvedCommentEntry', () => ({
(scope.needsCollapsing = contentLength > scope.contentLimit)
)
},
}))
}
})

View file

@ -14,7 +14,8 @@ import _ from 'lodash'
*/
import App from '../../../base'
export default App.directive('resolvedCommentsDropdown', () => ({
export default App.directive('resolvedCommentsDropdown', function () {
return {
restrict: 'E',
templateUrl: 'resolvedCommentsDropdownTemplate',
scope: {
@ -106,4 +107,5 @@ export default App.directive('resolvedCommentsDropdown', () => ({
})()
})
},
}))
}
})

View file

@ -12,7 +12,8 @@ import App from '../../../base'
export default App.directive('reviewPanelCollapseHeight', [
'$parse',
$parse => ({
function ($parse) {
return {
restrict: 'A',
link(scope, element, attrs) {
return scope.$watch(
@ -33,5 +34,6 @@ export default App.directive('reviewPanelCollapseHeight', [
}
)
},
}),
}
},
])

View file

@ -16,7 +16,8 @@
import App from '../../../base'
import { debugConsole } from '@/utils/debugging'
export default App.directive('reviewPanelSorted', () => ({
export default App.directive('reviewPanelSorted', function () {
return {
link(scope, element, attrs) {
let previous_focused_entry_index = 0
@ -277,7 +278,9 @@ export default App.directive('reviewPanelSorted', () => ({
scope.reviewPanelEventsBridge.emit('externalScroll', scrollTop)
scope.reviewPanelEventsBridge.on('aceScroll', scrollPanel)
scope.$on('$destroy', () => scope.reviewPanelEventsBridge.off('aceScroll'))
scope.$on('$destroy', () =>
scope.reviewPanelEventsBridge.off('aceScroll')
)
// receive the scroll position from the CodeMirror 6 track changes extension
window.addEventListener('editor:scroll', event => {
@ -297,4 +300,5 @@ export default App.directive('reviewPanelSorted', () => ({
return scope.reviewPanelEventsBridge.emit('refreshScrollPosition')
},
}))
}
})

View file

@ -12,7 +12,8 @@
*/
import App from '../../../base'
export default App.directive('reviewPanelToggle', () => ({
export default App.directive('reviewPanelToggle', function () {
return {
restrict: 'E',
scope: {
onToggle: '&',
@ -48,4 +49,5 @@ export default App.directive('reviewPanelToggle', () => ({
<label for="input-switch-{{$id}}" class="input-switch-btn"></label>
</fieldset>\
`,
}))
}
})