feat: update eslint config

support async component, see: https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser
This commit is contained in:
Dominik Pschenitschni 2022-10-04 12:48:23 +02:00
parent f360ebfe98
commit 4655e1ce34
No known key found for this signature in database
GPG key ID: B257AC0149F43A77
22 changed files with 57 additions and 47 deletions

View file

@ -1,3 +1,6 @@
/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution")
module.exports = { module.exports = {
'root': true, 'root': true,
'env': { 'env': {
@ -9,7 +12,7 @@ module.exports = {
'extends': [ 'extends': [
'eslint:recommended', 'eslint:recommended',
'plugin:vue/vue3-essential', 'plugin:vue/vue3-essential',
'@vue/typescript', '@vue/eslint-config-typescript/recommended',
], ],
'rules': { 'rules': {
'vue/html-quotes': [ 'vue/html-quotes': [
@ -28,7 +31,6 @@ module.exports = {
'error', 'error',
'never', 'never',
], ],
'vue/script-setup-uses-vars': 'error',
// see https://segmentfault.com/q/1010000040813116/a-1020000041134455 (original in chinese) // see https://segmentfault.com/q/1010000040813116/a-1020000041134455 (original in chinese)
'no-unused-vars': 'off', 'no-unused-vars': 'off',
@ -40,6 +42,7 @@ module.exports = {
'parserOptions': { 'parserOptions': {
'parser': '@typescript-eslint/parser', 'parser': '@typescript-eslint/parser',
'ecmaVersion': 2022, 'ecmaVersion': 2022,
'sourceType': 'module',
}, },
'ignorePatterns': [ 'ignorePatterns': [
'*.test.*', '*.test.*',

View file

@ -67,6 +67,7 @@
"@cypress/vite-dev-server": "3.2.0", "@cypress/vite-dev-server": "3.2.0",
"@cypress/vue": "4.2.0", "@cypress/vue": "4.2.0",
"@faker-js/faker": "7.5.0", "@faker-js/faker": "7.5.0",
"@rushstack/eslint-patch": "^1.2.0",
"@types/dompurify": "2.3.4", "@types/dompurify": "2.3.4",
"@types/flexsearch": "0.7.3", "@types/flexsearch": "0.7.3",
"@types/marked": "4.0.7", "@types/marked": "4.0.7",

View file

@ -11,6 +11,7 @@ specifiers:
'@fortawesome/vue-fontawesome': 3.0.1 '@fortawesome/vue-fontawesome': 3.0.1
'@github/hotkey': 2.0.1 '@github/hotkey': 2.0.1
'@kyvg/vue3-notification': 2.4.1 '@kyvg/vue3-notification': 2.4.1
'@rushstack/eslint-patch': ^1.2.0
'@sentry/tracing': 7.14.1 '@sentry/tracing': 7.14.1
'@sentry/vue': 7.14.1 '@sentry/vue': 7.14.1
'@types/dompurify': 2.3.4 '@types/dompurify': 2.3.4
@ -133,6 +134,7 @@ devDependencies:
'@cypress/vite-dev-server': 3.2.0 '@cypress/vite-dev-server': 3.2.0
'@cypress/vue': 4.2.0_cypress@10.9.0+vue@3.2.40 '@cypress/vue': 4.2.0_cypress@10.9.0+vue@3.2.40
'@faker-js/faker': 7.5.0 '@faker-js/faker': 7.5.0
'@rushstack/eslint-patch': 1.2.0
'@types/dompurify': 2.3.4 '@types/dompurify': 2.3.4
'@types/flexsearch': 0.7.3 '@types/flexsearch': 0.7.3
'@types/marked': 4.0.7 '@types/marked': 4.0.7
@ -2667,6 +2669,10 @@ packages:
rollup: 2.79.1 rollup: 2.79.1
dev: true dev: true
/@rushstack/eslint-patch/1.2.0:
resolution: {integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==}
dev: true
/@samverschueren/stream-to-observable/0.3.1_rxjs@6.6.7: /@samverschueren/stream-to-observable/0.3.1_rxjs@6.6.7:
resolution: {integrity: sha512-c/qwwcHyafOQuVQJj0IlBjf5yYgBI7YPJ77k4fOJYesb41jio65eaJODRUmfYKhTOFBrIZ66kgvGPlNbjuoRdQ==} resolution: {integrity: sha512-c/qwwcHyafOQuVQJj0IlBjf5yYgBI7YPJ77k4fOJYesb41jio65eaJODRUmfYKhTOFBrIZ66kgvGPlNbjuoRdQ==}
engines: {node: '>=6'} engines: {node: '>=6'}

View file

@ -578,7 +578,7 @@ export default defineComponent({
return return
} }
let ids = [] const ids = []
this[kind].forEach(u => { this[kind].forEach(u => {
ids.push(kind === 'users' ? u.username : u.id) ids.push(kind === 'users' ? u.username : u.id)
}) })
@ -613,7 +613,7 @@ export default defineComponent({
return return
} }
let labelIDs = [] const labelIDs = []
this.labels.forEach(u => { this.labels.forEach(u => {
labelIDs.push(u.id) labelIDs.push(u.id)
}) })

View file

@ -75,8 +75,8 @@ async function load() {
await router.push(redirectTo) await router.push(redirectTo)
} }
ready.value = true ready.value = true
} catch (e: any) { } catch (e: unknown) {
error.value = e error.value = String(e)
} }
} }

View file

@ -214,7 +214,7 @@ async function addTask() {
return rel return rel
}) })
await Promise.all(relations) await Promise.all(relations)
} catch (e: any) { } catch (e: { message?: string }) {
newTaskTitle.value = taskTitleBackup newTaskTitle.value = taskTitleBackup
if (e?.message === 'NO_LIST') { if (e?.message === 'NO_LIST') {
errorMessage.value = t('list.create.addListRequired') errorMessage.value = t('list.create.addListRequired')

View file

@ -278,13 +278,13 @@ export default defineComponent({
prepareGanttDays() { prepareGanttDays() {
console.debug('prepareGanttDays; start date: ', this.startDate, 'end date:', this.endDate) console.debug('prepareGanttDays; start date: ', this.startDate, 'end date:', this.endDate)
// Layout: years => [months => [days]] // Layout: years => [months => [days]]
let years = {} const years = {}
for ( for (
let d = this.startDate; let d = this.startDate;
d <= this.endDate; d <= this.endDate;
d.setDate(d.getDate() + 1) d.setDate(d.getDate() + 1)
) { ) {
let date = new Date(d) const date = new Date(d)
if (years[date.getFullYear() + ''] === undefined) { if (years[date.getFullYear() + ''] === undefined) {
years[date.getFullYear() + ''] = {} years[date.getFullYear() + ''] = {}
} }
@ -353,7 +353,7 @@ export default defineComponent({
const didntHaveDates = newTask.startDate === null ? true : false const didntHaveDates = newTask.startDate === null ? true : false
let startDate = new Date(this.startDate) const startDate = new Date(this.startDate)
startDate.setDate( startDate.setDate(
startDate.getDate() + newRect.left / this.dayWidth, startDate.getDate() + newRect.left / this.dayWidth,
) )
@ -362,7 +362,7 @@ export default defineComponent({
startDate.setUTCSeconds(0) startDate.setUTCSeconds(0)
startDate.setUTCMilliseconds(0) startDate.setUTCMilliseconds(0)
newTask.startDate = startDate newTask.startDate = startDate
let endDate = new Date(startDate) const endDate = new Date(startDate)
endDate.setDate( endDate.setDate(
startDate.getDate() + newRect.width / this.dayWidth, startDate.getDate() + newRect.width / this.dayWidth,
) )
@ -430,7 +430,7 @@ export default defineComponent({
if (!this.newTaskFieldActive) { if (!this.newTaskFieldActive) {
return return
} }
let task = new TaskModel({ const task = new TaskModel({
title: this.newTaskTitle, title: this.newTaskTitle,
listId: this.listId, listId: this.listId,
}) })

View file

@ -348,7 +348,7 @@ async function toggleTaskDone(task: ITask) {
// Find the task in the list and update it so that it is correctly strike through // Find the task in the list and update it so that it is correctly strike through
Object.entries(relatedTasks.value).some(([kind, tasks]) => { Object.entries(relatedTasks.value).some(([kind, tasks]) => {
return tasks.some((t, key) => { return (tasks as ITask[]).some((t, key) => {
const found = t.id === task.id const found = t.id === task.id
if (found) { if (found) {
relatedTasks.value[kind as IRelationKind]![key] = task relatedTasks.value[kind as IRelationKind]![key] = task

View file

@ -1,6 +1,8 @@
export default { import type {Directive} from 'vue'
const focus = <Directive<HTMLElement,string>>{
// When the bound element is inserted into the DOM... // When the bound element is inserted into the DOM...
mounted: (el, {modifiers}) => { mounted(el, {modifiers}) {
// Focus the element only if the viewport is big enough // Focus the element only if the viewport is big enough
// auto focusing elements on mobile can be annoying since in these cases the // auto focusing elements on mobile can be annoying since in these cases the
// keyboard always pops up and takes half of the available space on the screen. // keyboard always pops up and takes half of the available space on the screen.
@ -10,3 +12,5 @@ export default {
} }
}, },
} }
export default focus

View file

@ -4,7 +4,7 @@ import type {IAttachment} from '@/modelTypes/IAttachment'
import AttachmentService from '@/services/attachment' import AttachmentService from '@/services/attachment'
import {useTaskStore} from '@/stores/tasks' import {useTaskStore} from '@/stores/tasks'
export function uploadFile(taskId: number, file: File, onSuccess: (url: string) => void) { export function uploadFile(taskId: number, file: File, onSuccess?: (url: string) => void) {
const attachmentService = new AttachmentService() const attachmentService = new AttachmentService()
const files = [file] const files = [file]
@ -15,7 +15,7 @@ export async function uploadFiles(
attachmentService: AttachmentService, attachmentService: AttachmentService,
taskId: number, taskId: number,
files: File[] | FileList, files: File[] | FileList,
onSuccess: Function = () => {}, onSuccess?: (attachmentUrl: string) => void,
) { ) {
const attachmentModel = new AttachmentModel({taskId}) const attachmentModel = new AttachmentModel({taskId})
const response = await attachmentService.create(attachmentModel, files) const response = await attachmentService.create(attachmentModel, files)
@ -26,7 +26,7 @@ export async function uploadFiles(
taskId, taskId,
attachment, attachment,
}) })
onSuccess(generateAttachmentUrl(taskId, attachment.id)) onSuccess?.(generateAttachmentUrl(taskId, attachment.id))
}) })
if (response.errors !== null) { if (response.errors !== null) {

View file

@ -45,7 +45,6 @@ export async function refreshToken(persist: boolean): Promise<AxiosResponse> {
return response return response
} catch(e) { } catch(e) {
// @ts-ignore
throw new Error('Error renewing token: ', { cause: e }) throw new Error('Error renewing token: ', { cause: e })
} }
} }

View file

@ -1,19 +1,18 @@
export const calculateItemPosition = (positionBefore: number | null, positionAfter: number | null): number => { export const calculateItemPosition = (positionBefore: number | null, positionAfter: number | null): number => {
if (positionBefore === null && positionAfter === null) { if (positionBefore === null) {
if (positionAfter === null) {
return 0 return 0
} }
// If there is no task before, our task is the first task in which case we let it have half of the position of the task after it // If there is no task after it, we just add 2^16 to the last position to have enough room in the future
if (positionBefore === null && positionAfter !== null) {
return positionAfter / 2 return positionAfter / 2
} }
// If there is no task after it, we just add 2^16 to the last position to have enough room in the future // If there is no task after it, we just add 2^16 to the last position to have enough room in the future
if (positionBefore !== null && positionAfter === null) { if (positionAfter === null) {
return positionBefore + Math.pow(2, 16) return positionBefore + Math.pow(2, 16)
} }
// If we have both a task before and after it, we acually calculate the position // If we have both a task before and after it, we acually calculate the position
// @ts-ignore - can never be null but TS does not seem to understand that
return positionBefore + (positionAfter - positionBefore) / 2 return positionBefore + (positionAfter - positionBefore) / 2
} }

View file

@ -1,5 +1,5 @@
// https://stackoverflow.com/a/32108184/10924593 // https://stackoverflow.com/a/32108184/10924593
export function objectIsEmpty(obj: any): boolean { export function objectIsEmpty(obj: Record<string, unknown>): boolean {
return obj return obj
&& Object.keys(obj).length === 0 && Object.keys(obj).length === 0
&& Object.getPrototypeOf(obj) === Object.prototype && Object.getPrototypeOf(obj) === Object.prototype

View file

@ -3,7 +3,7 @@ import {parseURL} from 'ufo'
import {createRandomID} from '@/helpers/randomId' import {createRandomID} from '@/helpers/randomId'
import type {IProvider} from '@/types/IProvider' import type {IProvider} from '@/types/IProvider'
export const redirectToProvider = (provider: IProvider, redirectUrl: string = '') => { export const redirectToProvider = (provider: IProvider, redirectUrl = '') => {
// We're not using the redirect url provided by the server to allow redirects when using the electron app. // We're not using the redirect url provided by the server to allow redirects when using the electron app.
// The implications are not quite clear yet hence the logic to pass in another redirect url still exists. // The implications are not quite clear yet hence the logic to pass in another redirect url still exists.

View file

@ -125,16 +125,16 @@ const addTimeToDate = (text: string, date: Date, previousMatch: string | null):
} }
export const getDateFromText = (text: string, now: Date = new Date()) => { export const getDateFromText = (text: string, now: Date = new Date()) => {
const fullDateRegex: RegExp = / ([0-9][0-9]?\/[0-9][0-9]?\/[0-9][0-9]([0-9][0-9])?|[0-9][0-9][0-9][0-9]\/[0-9][0-9]?\/[0-9][0-9]?|[0-9][0-9][0-9][0-9]-[0-9][0-9]?-[0-9][0-9]?)/ig const fullDateRegex = / ([0-9][0-9]?\/[0-9][0-9]?\/[0-9][0-9]([0-9][0-9])?|[0-9][0-9][0-9][0-9]\/[0-9][0-9]?\/[0-9][0-9]?|[0-9][0-9][0-9][0-9]-[0-9][0-9]?-[0-9][0-9]?)/ig
// 1. Try parsing the text as a "usual" date, like 2021-06-24 or 06/24/2021 // 1. Try parsing the text as a "usual" date, like 2021-06-24 or 06/24/2021
let results: string[] | null = fullDateRegex.exec(text) let results: string[] | null = fullDateRegex.exec(text)
let result: string | null = results === null ? null : results[0] let result: string | null = results === null ? null : results[0]
let foundText: string | null = result let foundText: string | null = result
let containsYear: boolean = true let containsYear = true
if (result === null) { if (result === null) {
// 2. Try parsing the date as something like "jan 21" or "21 jan" // 2. Try parsing the date as something like "jan 21" or "21 jan"
const monthRegex: RegExp = new RegExp(` (${monthsRegexGroup} [0-9][0-9]?|[0-9][0-9]? ${monthsRegexGroup})`, 'ig') const monthRegex = new RegExp(` (${monthsRegexGroup} [0-9][0-9]?|[0-9][0-9]? ${monthsRegexGroup})`, 'ig')
results = monthRegex.exec(text) results = monthRegex.exec(text)
result = results === null ? null : `${results[0]} ${now.getFullYear()}`.trim() result = results === null ? null : `${results[0]} ${now.getFullYear()}`.trim()
foundText = results === null ? '' : results[0].trim() foundText = results === null ? '' : results[0].trim()
@ -142,7 +142,7 @@ export const getDateFromText = (text: string, now: Date = new Date()) => {
if (result === null) { if (result === null) {
// 3. Try parsing the date as "27/01" or "01/27" // 3. Try parsing the date as "27/01" or "01/27"
const monthNumericRegex: RegExp = / ([0-9][0-9]?\/[0-9][0-9]?)/ig const monthNumericRegex = / ([0-9][0-9]?\/[0-9][0-9]?)/ig
results = monthNumericRegex.exec(text) results = monthNumericRegex.exec(text)
// Put the year before or after the date, depending on what works // Put the year before or after the date, depending on what works
@ -229,7 +229,7 @@ export const getDateFromTextIn = (text: string, now: Date = new Date()) => {
} }
const getDateFromWeekday = (text: string): dateFoundResult => { const getDateFromWeekday = (text: string): dateFoundResult => {
const matcher: RegExp = / (next )?(monday|mon|tuesday|tue|wednesday|wed|thursday|thu|friday|fri|saturday|sat|sunday|sun)($| )/g const matcher = / (next )?(monday|mon|tuesday|tue|wednesday|wed|thursday|thu|friday|fri|saturday|sat|sunday|sun)($| )/g
const results: string[] | null = matcher.exec(text.toLowerCase()) // The i modifier does not seem to work. const results: string[] | null = matcher.exec(text.toLowerCase()) // The i modifier does not seem to work.
if (results === null) { if (results === null) {
return { return {
@ -240,7 +240,7 @@ const getDateFromWeekday = (text: string): dateFoundResult => {
const date: Date = new Date() const date: Date = new Date()
const currentDay: number = date.getDay() const currentDay: number = date.getDay()
let day: number = 0 let day = 0
switch (results[2]) { switch (results[2]) {
case 'mon': case 'mon':

View file

@ -1,12 +1,11 @@
export function parseDateOrString(rawValue: string | undefined, fallback: any): string | Date { export function parseDateOrString(rawValue: string | undefined, fallback: unknown) {
if (typeof rawValue === 'undefined') { if (typeof rawValue === 'undefined') {
return fallback return fallback
} }
const d = new Date(rawValue) const d = new Date(rawValue)
// @ts-ignore if rawValue is an invalid date, isNan will return false. return !isNaN(+d)
return !isNaN(d)
? d ? d
: rawValue : rawValue
} }

View file

@ -15,7 +15,7 @@ export function isNil(value: unknown) {
return value == null return value == null
} }
export function omitBy(obj: {}, check: (value: unknown) => boolean) { export function omitBy(obj: Record<string, unknown>, check: (value: unknown) => boolean) {
if (isNil(obj)) { if (isNil(obj)) {
return {} return {}
} }

View file

@ -31,15 +31,14 @@ export const createNewIndexer = (name: string, fieldsToIndex: string[]) => {
return index.update(item.id, item) return index.update(item.id, item)
} }
function search(query: string | null): number[] | null { function search(query: string | null) {
if (query === '' || query === null) { if (query === '' || query === null) {
return null return null
} }
// @ts-ignore
return index.search(query) return index.search(query)
?.flatMap(r => r.result) ?.flatMap(r => r.result)
.filter((value, index, self) => self.indexOf(value) === index) .filter((value, index, self) => self.indexOf(value) === index) as number[]
|| null || null
} }

View file

@ -15,7 +15,7 @@ export interface IList extends IAbstract {
isArchived: boolean isArchived: boolean
hexColor: string hexColor: string
identifier: string identifier: string
backgroundInformation: any // FIXME: improve type backgroundInformation: unknown | null // FIXME: improve type
isFavorite: boolean isFavorite: boolean
subscription: ISubscription subscription: ISubscription
position: number position: number

View file

@ -19,7 +19,7 @@ export default class ListModel extends AbstractModel<IList> implements IList {
isArchived = false isArchived = false
hexColor = '' hexColor = ''
identifier = '' identifier = ''
backgroundInformation: any = null backgroundInformation: unknown | null = null
isFavorite = false isFavorite = false
subscription: ISubscription = null subscription: ISubscription = null
position = 0 position = 0

View file

@ -149,7 +149,7 @@ export default abstract class AbstractService<Model extends IAbstract = IAbstrac
/** /**
* Returns a fully-ready-ready-to-make-a-request-to route with replaced parameters. * Returns a fully-ready-ready-to-make-a-request-to route with replaced parameters.
*/ */
getReplacedRoute(path : string, pathparams : {}) : string { getReplacedRoute(path : string, pathparams : Record<string, unknown>) : string {
const replacements = this.getRouteReplacements(path, pathparams) const replacements = this.getRouteReplacements(path, pathparams)
return Object.entries(replacements).reduce( return Object.entries(replacements).reduce(
(result, [parameter, value]) => result.replace(parameter, value as string), (result, [parameter, value]) => result.replace(parameter, value as string),

View file

@ -562,8 +562,8 @@ const hasAttachments = computed(() => attachmentStore.attachments.length > 0)
// HACK: // HACK:
const shouldShowClosePopup = computed(() => (route.name as string).includes('kanban')) const shouldShowClosePopup = computed(() => (route.name as string).includes('kanban'))
function attachmentUpload(...args: any[]) { function attachmentUpload(file: File, onSuccess?: (url: string) => void) {
return uploadFile(taskId.value, ...args) return uploadFile(taskId.value, file, onSuccess)
} }
const heading = ref<HTMLElement | null>(null) const heading = ref<HTMLElement | null>(null)