chore(lint): add lint plugin for using for loop instead of for...of over array (#587)

* Add prefer for loop

* change plugin name

* Revert tsconfig

* Revert random comment

* Revert eslint config parser options

---------

Co-authored-by: Sojin Park <raon0211@toss.im>
This commit is contained in:
Dayong Lee 2024-09-24 10:35:28 +09:00 committed by GitHub
parent 36b14bef50
commit f38392c400
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 165 additions and 66 deletions

View File

@ -4,6 +4,7 @@ import tseslint from 'typescript-eslint';
import jsdoc from 'eslint-plugin-jsdoc';
import prettier from 'eslint-config-prettier';
import pluginVue from 'eslint-plugin-vue';
import noForOfArrayPlugin from 'eslint-plugin-no-for-of-array';
export default [
{
@ -27,7 +28,6 @@ export default [
...globals['shared-node-browser'],
...globals.es2015,
},
parserOptions: {
ecmaFeatures: {
jsx: true,
@ -42,6 +42,26 @@ export default [
prettier,
jsdoc.configs['flat/recommended'],
...pluginVue.configs['flat/recommended'],
{
files: ['src/**/*.ts'],
ignores: ['**/*.spec.ts'],
languageOptions: {
parser: tseslint.parser,
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: import.meta.dirname,
ecmaFeatures: {
jsx: true,
},
},
},
plugins: {
'no-for-of-array': noForOfArrayPlugin,
},
rules: {
'no-for-of-array/no-for-of-array': 'error',
},
},
{
rules: {
'no-implicit-coercion': 'error',

View File

@ -147,6 +147,7 @@
"eslint": "^9.9.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jsdoc": "^50.2.2",
"eslint-plugin-no-for-of-array": "^0.0.1",
"eslint-plugin-vue": "^9.28.0",
"execa": "^9.3.0",
"globals": "^15.9.0",

View File

@ -14,7 +14,8 @@ type NotFalsey<T> = Exclude<T, false | null | 0 | 0n | '' | undefined>;
export function compact<T>(arr: readonly T[]): Array<NotFalsey<T>> {
const result: Array<NotFalsey<T>> = [];
for (const item of arr) {
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
if (item) {
result.push(item as NotFalsey<T>);
}

View File

@ -28,7 +28,8 @@
export function countBy<T, K extends PropertyKey>(arr: readonly T[], mapper: (item: T) => K): Record<K, number> {
const result = {} as Record<K, number>;
for (const item of arr) {
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
const key = mapper(item);
result[key] = (result[key] ?? 0) + 1;

View File

@ -33,7 +33,8 @@
export function groupBy<T, K extends PropertyKey>(arr: readonly T[], getKeyFromItem: (item: T) => K): Record<K, T[]> {
const result = Object.create(null) as Record<K, T[]>;
for (const item of arr) {
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
const key = getKeyFromItem(item);
if (result[key] == null) {

View File

@ -28,7 +28,8 @@
export function keyBy<T, K extends PropertyKey>(arr: readonly T[], getKeyFromItem: (item: T) => K): Record<K, T> {
const result = {} as Record<K, T>;
for (const item of arr) {
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
const key = getKeyFromItem(item);
result[key] = item;
}

View File

@ -64,7 +64,8 @@ export function maxBy<T>(items: readonly T[], getValue: (element: T) => number):
let maxElement = items[0];
let max = -Infinity;
for (const element of items) {
for (let i = 0; i < items.length; i++) {
const element = items[i];
const value = getValue(element);
if (value > max) {
max = value;

View File

@ -64,7 +64,8 @@ export function minBy<T>(items: readonly T[], getValue: (element: T) => number):
let minElement = items[0];
let min = Infinity;
for (const element of items) {
for (let i = 0; i < items.length; i++) {
const element = items[i];
const value = getValue(element);
if (value < min) {
min = value;

View File

@ -24,7 +24,8 @@ export function partition<T>(arr: readonly T[], isInTruthy: (value: T) => boolea
const truthy: T[] = [];
const falsy: T[] = [];
for (const item of arr) {
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
if (isInTruthy(item)) {
truthy.push(item);
} else {

View File

@ -19,7 +19,8 @@
export function takeWhile<T>(arr: readonly T[], shouldContinueTaking: (element: T) => boolean): T[] {
const result: T[] = [];
for (const item of arr) {
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
if (!shouldContinueTaking(item)) {
break;
}

View File

@ -23,7 +23,9 @@
export function unionBy<T, U>(arr1: readonly T[], arr2: readonly T[], mapper: (item: T) => U): T[] {
const map = new Map<U, T>();
for (const item of [...arr1, ...arr2]) {
const items = [...arr1, ...arr2];
for (let i = 0; i < items.length; i++) {
const item = items[i];
const key = mapper(item);
if (!map.has(key)) {

View File

@ -27,7 +27,8 @@
export function uniqBy<T, U>(arr: readonly T[], mapper: (item: T) => U): T[] {
const map = new Map<U, T>();
for (const item of arr) {
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
const key = mapper(item);
if (!map.has(key)) {

View File

@ -16,7 +16,8 @@
export function uniqWith<T>(arr: readonly T[], areItemsEqual: (item1: T, item2: T) => boolean): T[] {
const result: T[] = [];
for (const item of arr) {
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
const isUniq = result.every(v => !areItemsEqual(v, item));
if (isUniq) {

View File

@ -231,7 +231,7 @@ describe('debounce', () => {
expect(callCount).toBe(2);
});
it('subsequent debounced calls return the last `func` result', async done => {
it('subsequent debounced calls return the last `func` result', async () => {
const debounced = debounce(identity, 32);
debounced('a');
@ -383,7 +383,7 @@ describe('debounce', () => {
expect(callCount).toBe(2);
});
it('should support `maxWait` in a tight loop', async done => {
it('should support `maxWait` in a tight loop', async () => {
const limit = 1000;
let withCount = 0;
let withoutCount = 0;
@ -460,10 +460,12 @@ describe('debounce', () => {
const object = {};
const debounced = debounce(
function (this: any, value: any) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function (this: any, _: any) {
actual = [this];
// eslint-disable-next-line prefer-rest-params
Array.prototype.push.apply(actual, arguments as any);
return ++callCount != 2;
return ++callCount !== 2;
},
32,
{ leading: true, maxWait: 64 }
@ -505,7 +507,7 @@ describe('debounce', () => {
expect(callCount).toBe(isDebounce ? 1 : 2);
});
it(`\`_.${methodName}\` should invoke \`func\` with the correct \`this\` binding`, async done => {
it(`\`_.${methodName}\` should invoke \`func\` with the correct \`this\` binding`, async () => {
const actual: any[] = [];
const object = {
funced: func(function (this: any) {
@ -526,8 +528,10 @@ describe('debounce', () => {
const expected = args.slice();
const queue: any[] = args.slice();
var funced = func(function (this: any, _: unknown) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const funced = func(function (this: any, _: unknown) {
const current = [this];
// eslint-disable-next-line prefer-rest-params
Array.prototype.push.apply(current, arguments as any);
actual.push(current);

View File

@ -39,9 +39,8 @@ describe('throttle', () => {
expect(results2[0]).not.toStrictEqual(undefined);
});
it('should clear timeout when `func` is called', async done => {
it('should clear timeout when `func` is called', async () => {
let callCount = 0;
let dateCount = 0;
const throttled = throttle(() => {
callCount++;
@ -81,7 +80,7 @@ describe('throttle', () => {
options
);
const start = +new Date();
const start = Number(new Date());
while (Date.now() - start < limit) {
throttled();
}
@ -229,7 +228,7 @@ describe('throttle', () => {
expect(callCount).toBe(isDebounce ? 1 : 2);
});
it(`\`_.${methodName}\` should invoke \`func\` with the correct \`this\` binding`, async done => {
it(`\`_.${methodName}\` should invoke \`func\` with the correct \`this\` binding`, async () => {
const actual: any[] = [];
const object = {
funced: func(function (this: any) {
@ -250,8 +249,10 @@ describe('throttle', () => {
const expected = args.slice();
const queue: any[] = args.slice();
var funced = func(function (this: any, _: unknown) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const funced = func(function (this: any, _: unknown) {
const current = [this];
// eslint-disable-next-line prefer-rest-params
Array.prototype.push.apply(current, arguments as any);
actual.push(current);

View File

@ -37,7 +37,8 @@ export function max<T>(items: readonly T[] = []): T | undefined {
let maxElement = items[0];
let max: any = undefined;
for (const element of items) {
for (let i = 0; i < items.length; i++) {
const element = items[i];
if (max == null || element > max) {
max = element;
maxElement = element;

View File

@ -41,7 +41,8 @@ export function min<T>(items: readonly T[] = []): T {
let minElement = items[0];
let min: any = undefined;
for (const element of items) {
for (let i = 0; i < items.length; i++) {
const element = items[i];
if (min == null || element < min) {
min = element;
minElement = element;

View File

@ -68,9 +68,9 @@ export function random(minimum: number, maximum: number, floating?: boolean): nu
* const result3 = random(5, 5); // If the minimum is equal to the maximum, an error is thrown.
*/
export function random(...args: any[]): number {
let minimum: number = 0;
let maximum: number = 1;
let floating: boolean = false;
let minimum = 0;
let maximum = 1;
let floating = false;
switch (args.length) {
case 1: {
@ -91,6 +91,7 @@ export function random(...args: any[]): number {
maximum = args[1];
}
}
// eslint-disable-next-line no-fallthrough
case 3: {
if (typeof args[2] === 'object' && args[2] != null && args[2][args[1]] === args[0]) {
minimum = 0;

View File

@ -94,7 +94,8 @@ export function pick<
const result: any = {};
for (let keys of keysArr) {
for (let i = 0; i < keysArr.length; i++) {
let keys = keysArr[i];
switch (typeof keys) {
case 'object': {
if (!Array.isArray(keys)) {

View File

@ -36,7 +36,9 @@ export function conformsTo(
return Object.keys(source).length === 0;
}
for (const key of Object.keys(source)) {
const keys = Object.keys(source);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const predicate = source[key];
const value = target[key];
if ((value === undefined && !(key in target)) || !predicate(value)) {

View File

@ -1,26 +1,3 @@
interface DebounceTimer {
/**
* Checks if the timer is active.
* @returns {boolean} True if the timer is active, otherwise false.
*/
isActive: () => boolean;
/**
* Triggers the debounce timer.
* This method resets the timer and schedules the execution of the debounced function
* after the specified delay. If the timer is already active, it clears the existing timeout
* before setting a new one.
*/
trigger: () => void;
/**
* Cancels any pending execution of the debounced function.
* This method clears the active timer, ensuring that the function will not be called
* at the end of the debounce period. It also resets any stored context or arguments.
*/
cancel: () => void;
}
interface DebounceOptions {
/**
* An optional AbortSignal to cancel the debounced function.
@ -158,6 +135,7 @@ export function debounce<F extends (...args: any[]) => void>(
return;
}
// eslint-disable-next-line @typescript-eslint/no-this-alias
pendingThis = this;
pendingArgs = args;

View File

@ -18,7 +18,8 @@
export function omit<T extends Record<string, any>, K extends keyof T>(obj: T, keys: readonly K[]): Omit<T, K> {
const result = { ...obj };
for (const key of keys) {
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
delete result[key];
}

View File

@ -23,12 +23,12 @@ export function omitBy<T extends Record<string, any>>(
): Partial<T> {
const result: Partial<T> = {};
for (const [key, value] of Object.entries(obj)) {
if (shouldOmit(value, key)) {
continue;
const objEntries = Object.entries(obj);
for (let i = 0; i < objEntries.length; i++) {
const [key, value] = objEntries[i];
if (!shouldOmit(value, key)) {
(result as any)[key] = value;
}
(result as any)[key] = value;
}
return result;

View File

@ -18,7 +18,8 @@
export function pick<T extends Record<string, any>, K extends keyof T>(obj: T, keys: readonly K[]): Pick<T, K> {
const result = {} as Pick<T, K>;
for (const key of keys) {
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
result[key] = obj[key];
}

View File

@ -23,12 +23,12 @@ export function pickBy<T extends Record<string, any>>(
): Partial<T> {
const result: Partial<T> = {};
for (const [key, value] of Object.entries(obj)) {
if (!shouldPick(value, key)) {
continue;
const objEntries = Object.entries(obj);
for (let i = 0; i < objEntries.length; i++) {
const [key, value] = objEntries[i];
if (shouldPick(value, key)) {
(result as any)[key] = value;
}
(result as any)[key] = value;
}
return result;

View File

@ -16,7 +16,8 @@ import { getWords } from './_internal/getWords.ts';
export function startCase(str: string): string {
const words = getWords(str.trim());
let result = '';
for (const word of words) {
for (let i = 0; i < words.length; i++) {
const word = words[i];
if (result) {
result += ' ';
}

View File

@ -2851,6 +2851,16 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:8.6.0":
version: 8.6.0
resolution: "@typescript-eslint/scope-manager@npm:8.6.0"
dependencies:
"@typescript-eslint/types": "npm:8.6.0"
"@typescript-eslint/visitor-keys": "npm:8.6.0"
checksum: 10c0/37092ef70171c06854ac67ebfb2255063890c1c6133654e6b15b6adb6d2ab83de4feafd1599f4d02ed71a018226fcb3a389021758ec045e1904fb1798e90b4fe
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:8.1.0":
version: 8.1.0
resolution: "@typescript-eslint/type-utils@npm:8.1.0"
@ -2873,6 +2883,13 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/types@npm:8.6.0":
version: 8.6.0
resolution: "@typescript-eslint/types@npm:8.6.0"
checksum: 10c0/e7051d212252f7d1905b5527b211e335db4ec5bb1d3a52d73c8d2de6ddf5cbc981f2c92ca9ffcef35f7447bda635ea1ccce5f884ade7f243d14f2a254982c698
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:8.1.0":
version: 8.1.0
resolution: "@typescript-eslint/typescript-estree@npm:8.1.0"
@ -2892,6 +2909,25 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:8.6.0":
version: 8.6.0
resolution: "@typescript-eslint/typescript-estree@npm:8.6.0"
dependencies:
"@typescript-eslint/types": "npm:8.6.0"
"@typescript-eslint/visitor-keys": "npm:8.6.0"
debug: "npm:^4.3.4"
fast-glob: "npm:^3.3.2"
is-glob: "npm:^4.0.3"
minimatch: "npm:^9.0.4"
semver: "npm:^7.6.0"
ts-api-utils: "npm:^1.3.0"
peerDependenciesMeta:
typescript:
optional: true
checksum: 10c0/33ab8c03221a797865301f09d1d198c67f8b0e3dbf0d13e41f699dc2740242303a9fcfd7b38302cef318541fdedd832fd6e8ba34a5041a57e9114fa134045385
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:8.1.0":
version: 8.1.0
resolution: "@typescript-eslint/utils@npm:8.1.0"
@ -2906,6 +2942,20 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:^8.6.0":
version: 8.6.0
resolution: "@typescript-eslint/utils@npm:8.6.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.4.0"
"@typescript-eslint/scope-manager": "npm:8.6.0"
"@typescript-eslint/types": "npm:8.6.0"
"@typescript-eslint/typescript-estree": "npm:8.6.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
checksum: 10c0/5b615106342dfdf09f5a73e2554cc0c4d979c262a9a4548eb76ec7045768e0ff0bf0316cf8a5eb5404689cd476fcd335fc84f90eb985557559e42aeee33d687e
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.1.0":
version: 8.1.0
resolution: "@typescript-eslint/visitor-keys@npm:8.1.0"
@ -2916,6 +2966,16 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.6.0":
version: 8.6.0
resolution: "@typescript-eslint/visitor-keys@npm:8.6.0"
dependencies:
"@typescript-eslint/types": "npm:8.6.0"
eslint-visitor-keys: "npm:^3.4.3"
checksum: 10c0/9bd5d5daee9de7e009fdd1b64b1eca685a699d1b2639373bc279c97e25e769fff56fffef708ef66a2b19bc8bb201d36daf9e7084f0e0872178bfcf9d923b41f3
languageName: node
linkType: hard
"@vitejs/plugin-vue@npm:^5.0.5":
version: 5.1.2
resolution: "@vitejs/plugin-vue@npm:5.1.2"
@ -4927,6 +4987,7 @@ __metadata:
eslint: "npm:^9.9.0"
eslint-config-prettier: "npm:^9.1.0"
eslint-plugin-jsdoc: "npm:^50.2.2"
eslint-plugin-no-for-of-array: "npm:^0.0.1"
eslint-plugin-vue: "npm:^9.28.0"
execa: "npm:^9.3.0"
globals: "npm:^15.9.0"
@ -5336,6 +5397,19 @@ __metadata:
languageName: node
linkType: hard
"eslint-plugin-no-for-of-array@npm:^0.0.1":
version: 0.0.1
resolution: "eslint-plugin-no-for-of-array@npm:0.0.1"
dependencies:
"@typescript-eslint/utils": "npm:^8.6.0"
peerDependencies:
"@typescript-eslint/parser": ^8.6.0
eslint: ^9.11.0
typescript: ^5.6.2
checksum: 10c0/37aeb5fcd71b05a5cc3c10617febc74c7da51467f1149d2e9f1b9d322b7e5090e780f8efa79007979bb52a026b8212690af74e88b35745dbc9ab3f5f6c0f14d7
languageName: node
linkType: hard
"eslint-plugin-vue@npm:^9.28.0":
version: 9.28.0
resolution: "eslint-plugin-vue@npm:9.28.0"