Skip to content

Commit 5ef59ca

Browse files
authored
build(lint): enable no-array-for-each #6281
## Problem Many bugs, and confusing behavior stem from developers using an async function within a forEach. This is almost always not desired. Additionally, `forEach` can lead to bloated implementations when other methods like `some`, `find`, `reduce`, or `flatMap` are more applicable. According to https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-for-each.md It is also faster with `for ... of`. ## Solution - Add lint rule: https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-for-each.md - Migrate existing cases with `eslint CLI --fix` (75% of cases). - For remaining cases, mark as exception.
1 parent 745306d commit 5ef59ca

File tree

180 files changed

+777
-616
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

180 files changed

+777
-616
lines changed

.eslintrc.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ module.exports = {
158158
'unicorn/prefer-reflect-apply': 'error',
159159
'unicorn/prefer-string-trim-start-end': 'error',
160160
'unicorn/prefer-type-error': 'error',
161+
// Discourage `.forEach` because it can lead to accidental, incorrect use of async callbacks.
162+
'unicorn/no-array-for-each': 'error',
161163
'security-node/detect-child-process': 'error',
162164

163165
'header/header': [

packages/amazonq/src/inlineChat/controller/inlineChatController.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,9 @@ export class InlineChatController {
156156
}
157157

158158
private async reset() {
159-
this.listeners.forEach((listener) => listener.dispose())
159+
for (const listener of this.listeners) {
160+
listener.dispose()
161+
}
160162
this.listeners = []
161163

162164
this.task = undefined

packages/amazonq/src/inlineChat/output/computeDiff.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
5-
import { type LinesOptions, diffLines, Change } from 'diff'
5+
import { type LinesOptions, diffLines } from 'diff'
66
import * as vscode from 'vscode'
77
import { InlineTask, TextDiff } from '../controller/inlineTask'
88

@@ -24,8 +24,7 @@ export function computeDiff(response: string, inlineTask: InlineTask, isPartialD
2424

2525
const textDiff: TextDiff[] = []
2626
let startLine = selectedRange.start.line
27-
28-
diffs.forEach((part: Change) => {
27+
for (const part of diffs) {
2928
const count = part.count ?? 0
3029
if (part.removed) {
3130
if (part.value !== '\n') {
@@ -49,7 +48,7 @@ export function computeDiff(response: string, inlineTask: InlineTask, isPartialD
4948
}
5049
}
5150
startLine += count
52-
})
51+
}
5352
inlineTask.diff = textDiff
5453
return textDiff
5554
}

packages/amazonq/test/e2e/amazonq/explore.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ describe('Amazon Q Explore page', function () {
3636
it('should have correct button IDs', async () => {
3737
const features = ['featuredev', 'testgen', 'doc', 'review', 'gumby']
3838

39-
features.forEach((feature, index) => {
39+
for (const [index, feature] of features.entries()) {
4040
const buttons = (tab.getStore().chatItems ?? [])[index].buttons ?? []
4141
assert.deepStrictEqual(buttons[0].id, `user-guide-${feature}`)
4242
assert.deepStrictEqual(buttons[1].id, `quick-start-${feature}`)
43-
})
43+
}
4444
})
4545
})

packages/amazonq/test/e2e/amazonq/welcome.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ describe('Amazon Q Welcome page', function () {
5151
framework.createTab()
5252

5353
let welcomeCount = 0
54-
framework.getTabs().forEach((tab) => {
54+
for (const tab of framework.getTabs()) {
5555
if (tab.getStore().tabTitle === 'Welcome to Q') {
5656
welcomeCount++
5757
}
58-
})
58+
}
5959
// 3 welcome tabs
6060
assert.deepStrictEqual(welcomeCount, 3)
6161

packages/amazonq/test/unit/codewhisperer/service/keyStrokeHandler.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ describe('keyStrokeHandler', function () {
212212
['function suggestedByIntelliSense():', DocumentChangedSource.Unknown],
213213
]
214214

215-
cases.forEach((tuple) => {
215+
for (const tuple of cases) {
216216
const input = tuple[0]
217217
const expected = tuple[1]
218218
it(`test input ${input} should return ${expected}`, function () {
@@ -221,7 +221,7 @@ describe('keyStrokeHandler', function () {
221221
).checkChangeSource()
222222
assert.strictEqual(actual, expected)
223223
})
224-
})
224+
}
225225

226226
function createFakeDocumentChangeEvent(str: string): ReadonlyArray<vscode.TextDocumentContentChangeEvent> {
227227
return [

packages/amazonq/test/unit/codewhisperer/util/crossFileContextUtil.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,11 @@ describe('crossFileContextUtil', function () {
251251
const actual = await crossFile.getCrossFileCandidates(editor)
252252

253253
assert.ok(actual.length === 5)
254-
actual.forEach((actualFile, index) => {
254+
for (const [index, actualFile] of actual.entries()) {
255255
const expectedFile = path.join(tempFolder, expectedFilePaths[index])
256256
assert.strictEqual(normalize(expectedFile), normalize(actualFile))
257257
assert.ok(areEqual(tempFolder, actualFile, expectedFile))
258-
})
258+
}
259259
})
260260
})
261261

@@ -274,7 +274,7 @@ describe('crossFileContextUtil', function () {
274274
await closeAllEditors()
275275
})
276276

277-
fileExtLists.forEach((fileExt) => {
277+
for (const fileExt of fileExtLists) {
278278
it('should be empty if userGroup is control', async function () {
279279
const editor = await toTextEditor('content-1', `file-1.${fileExt}`, tempFolder)
280280
await toTextEditor('content-2', `file-2.${fileExt}`, tempFolder, { preview: false })
@@ -287,7 +287,7 @@ describe('crossFileContextUtil', function () {
287287

288288
assert.ok(actual && actual.supplementalContextItems.length === 0)
289289
})
290-
})
290+
}
291291
})
292292

293293
describe.skip('partial support - crossfile group', function () {
@@ -305,7 +305,7 @@ describe('crossFileContextUtil', function () {
305305
await closeAllEditors()
306306
})
307307

308-
fileExtLists.forEach((fileExt) => {
308+
for (const fileExt of fileExtLists) {
309309
it('should be non empty if usergroup is Crossfile', async function () {
310310
const editor = await toTextEditor('content-1', `file-1.${fileExt}`, tempFolder)
311311
await toTextEditor('content-2', `file-2.${fileExt}`, tempFolder, { preview: false })
@@ -318,7 +318,7 @@ describe('crossFileContextUtil', function () {
318318

319319
assert.ok(actual && actual.supplementalContextItems.length !== 0)
320320
})
321-
})
321+
}
322322
})
323323

324324
describe('full support', function () {
@@ -337,7 +337,7 @@ describe('crossFileContextUtil', function () {
337337
await closeAllEditors()
338338
})
339339

340-
fileExtLists.forEach((fileExt) => {
340+
for (const fileExt of fileExtLists) {
341341
it(`supplemental context for file ${fileExt} should be non empty`, async function () {
342342
sinon.stub(FeatureConfigProvider.instance, 'getProjectContextGroup').returns('control')
343343
sinon
@@ -361,7 +361,7 @@ describe('crossFileContextUtil', function () {
361361

362362
assert.ok(actual && actual.supplementalContextItems.length !== 0)
363363
})
364-
})
364+
}
365365
})
366366

367367
describe('splitFileToChunks', function () {

packages/amazonq/test/unit/codewhisperer/util/editorContext.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,12 @@ describe('editorContext', function () {
9595
['c', 'c'],
9696
])
9797

98-
languageToExtension.forEach((extension, language) => {
98+
for (const [language, extension] of languageToExtension.entries()) {
9999
const editor = createMockTextEditor('', 'test.ipynb', language, 1, 17)
100100
const actual = EditorContext.getFileRelativePath(editor)
101101
const expected = 'test.' + extension
102102
assert.strictEqual(actual, expected)
103-
})
103+
}
104104
})
105105

106106
it('Should return relative path', async function () {

packages/amazonq/test/unit/codewhisperer/util/runtimeLanguageContext.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,15 @@ describe('runtimeLanguageContext', function () {
5252
await resetCodeWhispererGlobalVariables()
5353
})
5454

55-
cases.forEach((tuple) => {
55+
for (const tuple of cases) {
5656
const languageId = tuple[0]
5757
const expected = tuple[1]
5858

5959
it(`should ${expected ? '' : 'not'} support ${languageId}`, function () {
6060
const actual = languageContext.isLanguageSupported(languageId)
6161
assert.strictEqual(actual, expected)
6262
})
63-
})
63+
}
6464

6565
describe('test isLanguageSupported with document as the argument', function () {
6666
const cases: [string, boolean][] = [
@@ -105,7 +105,7 @@ describe('runtimeLanguageContext', function () {
105105
['helloFoo.foo', false],
106106
]
107107

108-
cases.forEach((tuple) => {
108+
for (const tuple of cases) {
109109
const fileName = tuple[0]
110110
const expected = tuple[1]
111111

@@ -114,7 +114,7 @@ describe('runtimeLanguageContext', function () {
114114
const actual = languageContext.isLanguageSupported(doc)
115115
assert.strictEqual(actual, expected)
116116
})
117-
})
117+
}
118118
})
119119
})
120120

@@ -148,14 +148,14 @@ describe('runtimeLanguageContext', function () {
148148
[undefined, 'plaintext'],
149149
]
150150

151-
cases.forEach((tuple) => {
151+
for (const tuple of cases) {
152152
const vscLanguageId = tuple[0]
153153
const expectedCwsprLanguageId = tuple[1]
154154
it(`given vscLanguage ${vscLanguageId} should return ${expectedCwsprLanguageId}`, function () {
155155
const result = runtimeLanguageContext.getLanguageContext(vscLanguageId)
156156
assert.strictEqual(result.language as string, expectedCwsprLanguageId)
157157
})
158-
})
158+
}
159159
})
160160

161161
describe('normalizeLanguage', function () {

packages/amazonq/test/unit/codewhisperer/util/securityScanLanguageContext.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,15 @@ describe('securityScanLanguageContext', function () {
5151
await resetCodeWhispererGlobalVariables()
5252
})
5353

54-
cases.forEach((tuple) => {
54+
for (const tuple of cases) {
5555
const languageId = tuple[0]
5656
const expected = tuple[1]
5757

5858
it(`should ${expected ? '' : 'not'} support ${languageId}`, function () {
5959
const actual = languageContext.isLanguageSupported(languageId)
6060
assert.strictEqual(actual, expected)
6161
})
62-
})
62+
}
6363
})
6464

6565
describe('normalizeLanguage', function () {

0 commit comments

Comments
 (0)