manual lint

This commit is contained in:
Henry Jameson 2026-01-06 17:32:22 +02:00
commit 1c53ac84cc
36 changed files with 204 additions and 107 deletions

View file

@ -6,7 +6,7 @@
"useIgnoreFile": true
},
"files": {
"includes": ["**", "!!**/dist"]
"includes": ["**", "!!**/dist", "!!tools/emojis.json"]
},
"formatter": {
"enabled": true,
@ -91,8 +91,35 @@
},
"overrides": [
{
"includes": ["**/*.spec.js"],
"javascript": { "globals": ["describe", "it", "expect"] }
"includes": ["**/*.spec.js", "test/fixtures/*.js"],
"javascript": {
"globals": [
"vi",
"describe",
"it",
"test",
"expect",
"before",
"beforeEach",
"after",
"afterEach"
]
}
},
{
"includes": ["**/*.vue"],
"linter": {
"rules": {
"style": {
"useConst": "off",
"useImportType": "off"
},
"correctness": {
"noUnusedVariables": "off",
"noUnusedImports": "off"
}
}
}
}
],
"assist": {

View file

@ -1,6 +1,6 @@
import { access } from 'node:fs/promises'
import { resolve } from 'node:path'
import { langCodeToCldrName, languages } from '../src/i18n/languages.js'
import { languages } from '../src/i18n/languages.js'
const annotationsImportPrefix = '@kazvmoe-infra/unicode-emoji-json/annotations/'
const specialAnnotationsLocale = {
@ -24,6 +24,7 @@ const getAllAccessibleAnnotations = async (projectRoot) => {
await access(importFile)
return `'${lang}': () => import('${importModule}')`
} catch (e) {
console.error(e)
return
}
}),

View file

@ -30,7 +30,9 @@ export const devSwPlugin = ({ swSrc, swDest, transformSW, alias }) => {
return {
name: 'dev-sw-plugin',
apply: 'serve',
configResolved(conf) {},
configResolved() {
/* no-op */
},
resolveId(id) {
const name = id.startsWith('/') ? id.slice(1) : id
if (name === swDest) {

View file

@ -82,7 +82,9 @@ export default {
contrast: {
required: false,
type: Object,
default: () => ({}),
default: () => ({
/* no-op */
}),
},
showRatio: {
required: false,

View file

@ -5,7 +5,9 @@ const DialogModal = {
type: Boolean,
},
onCancel: {
default: () => {},
default: () => {
/* no-op */
},
type: Function,
},
},

View file

@ -45,7 +45,6 @@ library.add(
)
const NavPanel = {
props: ['forceExpand', 'forceEditMode'],
created() {},
components: {
BookmarkFoldersMenuContent,
ListsMenuContent,

View file

@ -1,7 +1,8 @@
import { flattenDeep, unescape } from 'lodash'
import { flattenDeep, unescape as ldUnescape } from 'lodash'
import HashtagLink from 'src/components/hashtag_link/hashtag_link.vue'
import { MENTIONS_LIMIT } from 'src/components/mentions_line/mentions_line.js'
import MentionsLine from 'src/components/mentions_line/mentions_line.vue'
import StillImage from 'src/components/still-image/still-image.vue'
import StillImageEmojiPopover from 'src/components/still-image/still-image-emoji-popover.vue'
import { convertHtmlToLines } from 'src/services/html_converter/html_line_converter.service.js'
import { convertHtmlToTree } from 'src/services/html_converter/html_tree_converter.service.js'
@ -268,7 +269,7 @@ export default {
const emptyText = item.trim() === ''
if (emptyText) return item
if (!encounteredTextReverse) encounteredTextReverse = true
return unescape(item)
return ldUnescape(item)
} else if (Array.isArray(item)) {
// Handle tag nodes
const [opener, children] = item

View file

@ -17,14 +17,18 @@ const BlockList = withLoadMore({
fetch: (props, $store) => $store.dispatch('fetchBlocks'),
select: (props, $store) =>
get($store.state.users.currentUser, 'blockIds', []),
destroy: () => {},
destroy: () => {
/* no-op */
},
childPropName: 'items',
})(SelectableList)
const MuteList = withLoadMore({
fetch: (props, $store) => $store.dispatch('fetchMutes'),
select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []),
destroy: () => {},
destroy: () => {
/* no-op */
},
childPropName: 'items',
})(SelectableList)

View file

@ -1,6 +1,8 @@
const Confirm = {
props: ['disabled'],
data: () => ({}),
data: () => ({
/* no-op */
}),
methods: {
confirm() {
this.$emit('confirm')

View file

@ -8,7 +8,9 @@ export default {
}),
},
},
data: () => ({}),
data: () => ({
/* no-op */
}),
computed: {
inProgress() {
return this.backupCodes.inProgress

View file

@ -56,8 +56,7 @@ import Preview from '../old_theme_tab/theme_preview.vue'
import VirtualDirectivesTab from './virtual_directives_tab.vue'
// helper for debugging
// eslint-disable-next-line no-unused-vars
const toValue = (x) => JSON.parse(JSON.stringify(x === undefined ? 'null' : x))
// const toValue = (x) => JSON.parse(JSON.stringify(x === undefined ? 'null' : x))
// helper to make states comparable
const normalizeStates = (states) =>

View file

@ -7,8 +7,7 @@ import { serializeShadow } from 'src/services/theme_data/iss_serializer.js'
import { computed, inject, ref, watch } from 'vue'
// helper for debugging
// eslint-disable-next-line no-unused-vars
const toValue = (x) => JSON.parse(JSON.stringify(x === undefined ? 'null' : x))
// const toValue = (x) => JSON.parse(JSON.stringify(x === undefined ? 'null' : x))
export default {
components: {

View file

@ -20,7 +20,7 @@ import {
faThumbtack,
faTimes,
} from '@fortawesome/free-solid-svg-icons'
import { unescape, uniqBy } from 'lodash'
import { unescape as ldUnescape, uniqBy } from 'lodash'
import MentionLink from 'src/components/mention_link/mention_link.vue'
import MentionsLine from 'src/components/mentions_line/mentions_line.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
@ -425,7 +425,7 @@ const Status = {
},
replySubject() {
if (!this.status.summary) return ''
const decodedSummary = unescape(this.status.summary)
const decodedSummary = ldUnescape(this.status.summary)
const behavior = this.mergedConfig.subjectLineBehavior
const startsWithRe = decodedSummary.match(/^re[: ]/i)
if ((behavior !== 'noop' && startsWithRe) || behavior === 'masto') {

View file

@ -123,7 +123,12 @@ export default {
this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
}
},
doActionWrap(button, close = () => {}) {
doActionWrap(
button,
close = () => {
/* no-op */
},
) {
if (
this.button.interactive ? !this.button.interactive(this.funcArg) : false
)

View file

@ -20,7 +20,9 @@ const StatusActionButtons = {
currentConfirmTitle: '',
currentConfirmOkText: '',
currentConfirmCancelText: '',
currentConfirmAction: () => {},
currentConfirmAction: () => {
/* no-op */
},
randomSeed: genRandomSeed(),
}
},

View file

@ -15,7 +15,7 @@
:func-arg="funcArg"
:get-class="getClass"
:get-component="getComponent"
:close="() => {}"
:close="() => { /* no-op */ }"
:do-action="doAction"
@interacted="e => $emit('interacted')"
/>

View file

@ -54,10 +54,15 @@ const ThreadTree = {
return selfSuspendable
},
reverseLookupTable() {
return this.conversation.reduce((table, status, index) => {
table[status.id] = index
return table
}, {})
return this.conversation.reduce(
(table, status, index) => {
table[status.id] = index
return table
},
{
/* no-op */
},
)
},
currentReplies() {
return this.getReplies(this.status.id).map(({ id }) =>
@ -75,9 +80,15 @@ const ThreadTree = {
statusById(id) {
return this.conversation[this.reverseLookupTable[id]]
},
collapseThread() {},
showThread() {},
showAllSubthreads() {},
collapseThread() {
/* no-op */
},
showThread() {
/* no-op */
},
showAllSubthreads() {
/* no-op */
},
toggleCurrentProp(name) {
this.toggleStatusContentProperty(this.status.id, name)
},

View file

@ -13,7 +13,7 @@ import {
} from '@fortawesome/free-solid-svg-icons'
import isEqual from 'lodash/isEqual'
import merge from 'lodash/merge'
import unescape from 'lodash/unescape'
import ldUnescape from 'lodash/unescape'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import ColorInput from 'src/components/color_input/color_input.vue'
import DialogModal from 'src/components/dialog_modal/dialog_modal.vue'
@ -128,7 +128,7 @@ export default {
newName: user.name_unescaped,
editingName: false,
newBio: unescape(user.description),
newBio: ldUnescape(user.description),
editingBio: false,
newAvatar: null,

View file

@ -688,7 +688,6 @@
"name_bio": "Name & bio",
"new_email": "New email",
"new_password": "New password",
"posts": "Posts",
"user_profiles": "User Profiles",
"notification_visibility": "Types of notifications to show",
"notification_visibility_in_column": "Show in notifications column/drawer",
@ -777,7 +776,6 @@
"column_sizes_sidebar": "Sidebar",
"column_sizes_content": "Content",
"column_sizes_notifs": "Notifications",
"layout": "Layout",
"scale_and_font": "Scale and Font",
"theme_editor_min_width": "Minimum width of theme editor (0 for \"fit-content\")",
"tree_advanced": "Allow more flexible navigation in tree view",
@ -1188,9 +1186,6 @@
"restrictions": "Restrictions",
"activitypub": "ActivityPub"
},
"http": {
"outbound": "Outbound HTTP requests"
},
"auth": {
"MFA": "Multi-factor Authentication",
"LDAP": "LDAP Settings",
@ -1245,24 +1240,6 @@
"web_push": "Web Push",
"web_push_description": "Web Push VAPID settings. You can use the mix task web_push.gen.keypair to generate it."
},
"instance": {
"instance": "Instance information",
"branding": "Branding",
"registrations": "User sign-ups",
"captcha_header": "CAPTCHA",
"kocaptcha": "KoCaptcha settings",
"access": "Instance access",
"rich_metadata": "Metadata",
"restrict": {
"header": "Restrict access for anonymous visitors",
"description": "Detailed setting for allowing/disallowing access to certain aspects of API. By default (indeterminate state) it will disallow if instance is not public, ticked checkbox means disallow access even if instance is public, unticked means allow access even if instance is private. Please note that unexpected behavior might happen if some settings are set, i.e. if profile access is disabled posts will show without profile information.",
"timelines": "Timelines access",
"profiles": "User profiles access",
"activities": "Statuses/activities access"
},
":unauthenticated": "Unauthenticated",
":all": "Everyone"
},
"registrations": {
"welcome": {
"title": "Welcome message",
@ -1394,6 +1371,9 @@
},
"instance": {
"instance": "Instance information",
"registrations": "User sign-ups",
"captcha_header": "CAPTCHA",
"kocaptcha": "KoCaptcha settings",
"pwa": {
"manifest": "PWA Manifest",
"optional": "(optional)",
@ -1530,7 +1510,7 @@
"weeks_suffix": "week(s)",
"years": "{0} year | {0} years",
"years_short": "{0}y",
"years": "year(s)"
"years_suffix": "year(s)"
},
"in_future": "in {0}",
"in_past": "{0} ago",
@ -1779,7 +1759,6 @@
"repeat": "Repeat",
"unrepeat": "Unrepeat",
"reply": "Reply",
"add_reaction": "Add reaction",
"favorite": "Favorite",
"unfavorite": "Unfavorite",
"add_reaction": "Add Reaction",

View file

@ -166,8 +166,12 @@ export const piniaPersistPlugin =
afterLoad,
paths = [],
saveImmediatelyActions,
onSaveSuccess = () => {},
onSaveError = () => {},
onSaveSuccess = () => {
/* no-op */
},
onSaveError = () => {
/* no-op */
},
} = options.persist || {}
const loadedGuard = { loaded: false }

View file

@ -104,7 +104,8 @@ const persistedStateOptions = {
})()
// These are inlined by webpack's DefinePlugin
/* eslint-disable */
// biome-ignore-start lint: added in build process
window.___pleromafe_mode = process.env
window.___pleromafe_commit_hash = COMMIT_HASH
window.___pleromafe_dev_overrides = DEV_OVERRIDES
// biome-ignore-end lint: added in build process

View file

@ -30,7 +30,11 @@ const storageKey = 'pleroma-fe-drafts'
* different keys, which will just pollute the whole storage.
* It is indeed best to have backend support for this.
*/
const getStorageData = async () => (await storage.getItem(storageKey)) || {}
const getStorageData = async () =>
(await storage.getItem(storageKey)) ||
{
/* no-op */
}
const saveDraftToStorage = async (draft) => {
const currentData = await getStorageData()

View file

@ -550,7 +550,7 @@ const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) => {
}
const exportFriends = ({ id, credentials }) => {
// eslint-disable-next-line no-async-promise-executor
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO refactor this
return new Promise(async (resolve, reject) => {
try {
let friends = []
@ -1794,7 +1794,14 @@ export const ProcessedWS = ({
return eventTarget
}
export const handleMastoWS = (wsEvent, { onAuthenticated = () => {} } = {}) => {
export const handleMastoWS = (
wsEvent,
{
onAuthenticated = () => {
/* no-op */
},
} = {},
) => {
const { data } = wsEvent
if (!data) return
const parsedEvent = JSON.parse(data)

View file

@ -9,9 +9,13 @@ const fetchAndUpdate = ({ credentials }) => {
(bookmarkFolders) => {
useBookmarkFoldersStore().setBookmarkFolders(bookmarkFolders)
},
() => {},
(rej) => {
console.error(rej)
},
)
.catch(() => {})
.catch((e) => {
console.error(e)
})
}
const startFetching = ({ credentials, store }) => {

View file

@ -1,5 +1,5 @@
import { parseLinkHeader } from '@web3-storage/parse-link-header'
import escape from 'escape-html'
import escapeHtml from 'escape-html'
import punycode from 'punycode.js'
import { isStatusNotification } from '../notification_utils/notification_utils.js'
@ -69,7 +69,7 @@ export const parseUser = (data) => {
}
output.emoji = data.emojis
output.name = escape(data.display_name)
output.name = escapeHtml(data.display_name)
output.name_html = output.name
output.name_unescaped = data.display_name
@ -80,7 +80,7 @@ export const parseUser = (data) => {
output.fields = data.fields
output.fields_html = data.fields.map((field) => {
return {
name: escape(field.name),
name: escapeHtml(field.name),
value: field.value,
}
})
@ -367,13 +367,13 @@ export const parseStatus = (data) => {
output.retweeted_status = parseStatus(data.reblog)
}
output.summary_raw_html = escape(data.spoiler_text)
output.summary_raw_html = escapeHtml(data.spoiler_text)
output.external_url = data.url
output.poll = data.poll
if (output.poll) {
output.poll.options = (output.poll.options || []).map((field) => ({
...field,
title_html: escape(field.title),
title_html: escapeHtml(field.title),
}))
}
output.pinned = data.pinned

View file

@ -9,9 +9,13 @@ const fetchAndUpdate = ({ store, credentials }) => {
store.commit('setFollowRequests', requests)
store.commit('addNewUsers', requests)
},
() => {},
(rej) => {
console.error(rej)
},
)
.catch(() => {})
.catch((e) => {
console.error(e)
})
}
const startFetching = ({ credentials, store }) => {

View file

@ -93,12 +93,14 @@ class SwipeAndClickGesture {
perpendicularTolerance = 1.0,
disableClickThreshold = 1,
}) {
const nop = () => {}
const noop = () => {
/* no-op */
}
this.direction = direction
this.swipePreviewCallback = swipePreviewCallback || nop
this.swipeEndCallback = swipeEndCallback || nop
this.swipeCancelCallback = swipeCancelCallback || nop
this.swipelessClickCallback = swipelessClickCallback || nop
this.swipePreviewCallback = swipePreviewCallback || noop
this.swipeEndCallback = swipeEndCallback || noop
this.swipeCancelCallback = swipeCancelCallback || noop
this.swipelessClickCallback = swipelessClickCallback || noop
this.threshold =
typeof threshold === 'function' ? threshold : () => threshold
this.disableClickThreshold =

View file

@ -1,4 +1,4 @@
import { unescape } from 'lodash'
import { unescape as ldUnescape } from 'lodash'
import { getTagName } from './utility.service.js'
/**
@ -64,7 +64,7 @@ export const convertHtmlToTree = (html = '') => {
const handleOpen = (tag) => {
const curBuf = getCurrentBuffer()
const newLevel = [unescape(tag), []]
const newLevel = [ldUnescape(tag), []]
levels.push(newLevel)
curBuf.push(newLevel)
}

View file

@ -9,9 +9,13 @@ const fetchAndUpdate = ({ credentials }) => {
(lists) => {
useListsStore().setLists(lists)
},
() => {},
(rej) => {
console.error(rej)
},
)
.catch(() => {})
.catch((e) => {
console.error(e)
})
}
const startFetching = ({ credentials, store }) => {

View file

@ -77,9 +77,15 @@ const LAZY_STYLE_ID = 'pleroma-lazy-styles'
export const generateTheme = (inputRuleset, callbacks, debug) => {
const {
onNewRule = () => {},
onLazyFinished = () => {},
onEagerFinished = () => {},
onNewRule = () => {
/* no-op */
},
onLazyFinished = () => {
/* no-op */
},
onEagerFinished = () => {
/* no-op */
},
} = callbacks
const themes3 = init({
@ -152,8 +158,12 @@ export const tryLoadCache = async () => {
export const applyTheme = (
input,
onEagerFinish = () => {},
onFinish = () => {},
onEagerFinish = () => {
/* no-op */
},
onFinish = () => {
/* no-op */
},
debug,
) => {
const eagerStyles = createStyleSheet(EAGER_STYLE_ID, 10)

View file

@ -27,8 +27,12 @@ export const useInterfaceStore = defineStore('interface', {
themeDataUsed: null,
temporaryChangesTimeoutId: null,
temporaryChangesCountdown: -1, // used for temporary options that revert after a timeout
temporaryChangesConfirm: () => {}, // used for applying temporary options
temporaryChangesRevert: () => {}, // used for reverting temporary options
temporaryChangesConfirm: () => {
/* no-op */
}, // used for applying temporary options
temporaryChangesRevert: () => {
/* no-op */
}, // used for reverting temporary options
settingsModalState: 'hidden',
settingsModalLoadedUser: false,
settingsModalLoadedAdmin: false,
@ -73,8 +77,12 @@ export const useInterfaceStore = defineStore('interface', {
clearTimeout(this.temporaryChangesTimeoutId)
this.temporaryChangesTimeoutId = null
this.temporaryChangesCountdown = -1
this.temporaryChangesConfirm = () => {}
this.temporaryChangesRevert = () => {}
this.temporaryChangesConfirm = () => {
/* no-op */
}
this.temporaryChangesRevert = () => {
/* no-op */
}
},
setPageTitle(option = '') {
try {

View file

@ -8,7 +8,7 @@ export const injectMswToTest = (defaultHandlers) => {
return testBase.extend({
worker: [
async ({}, use) => {
async (_, use) => {
await worker.start()
await use(worker)

View file

@ -12,7 +12,11 @@ const applyAfterStore = (store, afterStore) => {
return store
}
const getDefaultOpts = ({ afterStore = () => {} } = {}) => ({
const getDefaultOpts = ({
afterStore = () => {
/* no-op */
},
} = {}) => ({
global: {
plugins: [
applyAfterStore(makeMockStore(), afterStore),

View file

@ -22,7 +22,9 @@ const generateInput = (value, padEmoji = true) => {
Popover: {
template: `<div><slot trigger /></div>`,
methods: {
updateStyles() {},
updateStyles() {
/* no-op */
},
},
},
},

View file

@ -5,12 +5,18 @@ import backendInteractorService from 'src/services/backend_interactor_service/ba
import { createStore } from 'vuex'
const mutations = {
clearTimeline: () => {},
clearTimeline: () => {
/* no-op */
},
}
const actions = {
fetchUser: () => {},
fetchUserByScreenName: () => {},
fetchUser: () => {
/* no-op */
},
fetchUserByScreenName: () => {
/* no-op */
},
}
const testGetters = {

View file

@ -34,7 +34,7 @@ beforeEach(() => {
describe('piniaPersistPlugin', () => {
describe('initial state', () => {
test('it does not load anything if it is not enabled', async () => {
it('does not load anything if it is not enabled', async () => {
await mockStorage.setItem('pinia-local-test', { a: 3 })
const useTestStore = defineStore('test', {
@ -62,7 +62,7 @@ describe('piniaPersistPlugin', () => {
await expect(test.$persistLoaded).rejects.toThrowError(error)
})
test('it loads from pinia storage', async () => {
it('loads from pinia storage', async () => {
await mockStorage.setItem('pinia-local-test', { a: 3, c: { d: 0 } })
await mockStorage.setItem('vuex-lz', { test: { a: 4 } })
@ -79,7 +79,7 @@ describe('piniaPersistPlugin', () => {
expect(test.c.e).to.eql(5)
})
test('it loads from vuex storage as fallback', async () => {
it('loads from vuex storage as fallback', async () => {
await mockStorage.setItem('vuex-lz', { test: { a: 4, c: { d: 0 } } })
const useTestStore = defineStore('test', {
@ -95,7 +95,7 @@ describe('piniaPersistPlugin', () => {
expect(test.c.e).to.eql(5)
})
test('it loads from vuex storage and writes it into pinia storage', async () => {
it('loads from vuex storage and writes it into pinia storage', async () => {
await mockStorage.setItem('vuex-lz', { test: { a: 4, c: { d: 0 } } })
const useTestStore = defineStore('test', {
@ -122,7 +122,7 @@ describe('piniaPersistPlugin', () => {
expect(test.c.e).to.eql(5)
})
test('it does not modify state if there is nothing to load', async () => {
it('does not modify state if there is nothing to load', async () => {
await mockStorage.setItem('vuex-lz', { test2: { a: 4 } })
const useTestStore = defineStore('test', {
@ -138,7 +138,7 @@ describe('piniaPersistPlugin', () => {
})
describe('paths', () => {
test('it saves everything if paths is unspecified', async () => {
it('saves everything if paths is unspecified', async () => {
const useTestStore = defineStore('test', {
state: () => ({ a: 1, b: 2 }),
persist: {},
@ -154,7 +154,7 @@ describe('piniaPersistPlugin', () => {
})
})
test('it saves only specified paths', async () => {
it('saves only specified paths', async () => {
const useTestStore = defineStore('test', {
state: () => ({ a: 1, b: 2, c: { d: 4, e: 5 } }),
persist: {
@ -173,7 +173,7 @@ describe('piniaPersistPlugin', () => {
})
})
test('it only saves after load', async () => {
it('only saves after load', async () => {
const onSaveError = vi.fn()
const onSaveSuccess = vi.fn()
const useTestStore = defineStore('test', {
@ -199,7 +199,7 @@ describe('piniaPersistPlugin', () => {
})
describe('saveImmediatelyActions', () => {
test('it should only persist state after specified actions', async () => {
it('should only persist state after specified actions', async () => {
const useTestStore = defineStore('test', {
state: () => ({ a: 1, b: 2 }),
actions: {
@ -285,7 +285,7 @@ describe('piniaPersistPlugin', () => {
})
describe('afterLoad', () => {
test('it is called with the saved state object', async () => {
it('is called with the saved state object', async () => {
await mockStorage.setItem('pinia-local-test', { a: 2 })
const afterLoad = vi.fn(async (orig) => {
return { a: orig.a + 1 }
@ -304,7 +304,7 @@ describe('piniaPersistPlugin', () => {
expect(test.a).to.eql(3)
})
test('it is called with empty object if there is no saved state', async () => {
it('is called with empty object if there is no saved state', async () => {
const afterLoad = vi.fn(async () => {
return { a: 3 }
})