Skip to content

Commit ea77516

Browse files
committed
Merge from master
2 parents f732d52 + 4c190f4 commit ea77516

File tree

925 files changed

+51601
-27680
lines changed

Some content is hidden

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

925 files changed

+51601
-27680
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "Resource Explorer: S3 tree view now shows bucket contents correctly, even when restricted to root prefix."
4+
}

.eslintrc.js

Lines changed: 15 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': [
@@ -178,6 +180,7 @@ module.exports = {
178180
'aws-toolkits/no-console-log': 'error',
179181
'aws-toolkits/no-json-stringify-in-log': 'error',
180182
'aws-toolkits/no-printf-mismatch': 'error',
183+
'aws-toolkits/no-index-import': 'error',
181184
'no-restricted-imports': [
182185
'error',
183186
{
@@ -199,8 +202,20 @@ module.exports = {
199202
name: 'fs',
200203
message: 'Avoid node:fs and use shared/fs/fs.ts when possible.',
201204
},
205+
{
206+
name: 'child_process',
207+
message:
208+
'Avoid child_process, use ChildProcess from `shared/utilities/processUtils.ts` instead.',
209+
},
210+
{
211+
name: '..',
212+
message:
213+
'Avoid importing from index.ts files as it can lead to circular dependencies. Import from the module directly instead.',
214+
},
202215
],
203216
},
204217
],
218+
219+
'prettier/prettier': ['error', { endOfLine: 'auto' }],
205220
},
206221
}

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,4 @@
88

99
- Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time.
1010
- Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines).
11-
12-
License: I confirm that my contribution is made under the terms of the Apache 2.0 license.
11+
- License: I confirm that my contribution is made under the terms of the Apache 2.0 license.
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
/**
2+
* Filters the report produced by jscpd to only include clones that involve changes from the given git diff.
3+
* If the filtered report is non-empty, i.e. there exists a clone in the changes,
4+
* the program exits with an error and logs the filtered report to console.
5+
*
6+
* Usage:
7+
* node filterDuplicates.js run [path_to_git_diff] [path_to_jscpd_report] [commit_hash] [repo_name]
8+
*
9+
* Tests:
10+
* node filterDuplicates.js test
11+
*/
12+
13+
const fs = require('fs/promises')
14+
const path = require('path')
15+
16+
function parseDiffFilePath(filePathLine) {
17+
return filePathLine.split(' ')[2].split('/').slice(1).join('/')
18+
}
19+
20+
function parseDiffRange(rangeLine) {
21+
const [_fromRange, toRange] = rangeLine.split(' ').slice(1, 3)
22+
const [startLine, numLines] = toRange.slice(1).split(',').map(Number)
23+
const range = [startLine, startLine + numLines]
24+
return range
25+
}
26+
27+
async function parseDiff(diffPath) {
28+
const diff = await fs.readFile(diffPath, 'utf8')
29+
const lines = diff.split('\n')
30+
let currentFile = null
31+
let currentFileChanges = []
32+
const fileChanges = new Map()
33+
34+
for (const line of lines) {
35+
if (line.startsWith('diff')) {
36+
if (currentFile) {
37+
fileChanges.set(currentFile, currentFileChanges)
38+
}
39+
currentFile = parseDiffFilePath(line)
40+
currentFileChanges = []
41+
}
42+
if (line.startsWith('@@')) {
43+
currentFileChanges.push(parseDiffRange(line))
44+
}
45+
}
46+
47+
fileChanges.set(currentFile, currentFileChanges)
48+
49+
return fileChanges
50+
}
51+
52+
function doesOverlap(range1, range2) {
53+
const [start1, end1] = range1
54+
const [start2, end2] = range2
55+
return (
56+
(start1 >= start2 && start1 <= end2) || (end1 >= start2 && end1 <= end2) || (start2 >= start1 && end2 <= end1)
57+
)
58+
}
59+
60+
function isCloneInChanges(changes, cloneInstance) {
61+
const fileName = cloneInstance.name
62+
const cloneStart = cloneInstance.start
63+
const cloneEnd = cloneInstance.end
64+
const lineChangeRanges = changes.get(fileName)
65+
66+
if (!lineChangeRanges) {
67+
return false
68+
}
69+
70+
return lineChangeRanges.some((range) => doesOverlap([cloneStart, cloneEnd], range))
71+
}
72+
73+
function isInChanges(changes, dupe) {
74+
return isCloneInChanges(changes, dupe.firstFile) || isCloneInChanges(changes, dupe.secondFile)
75+
}
76+
77+
function filterDuplicates(report, changes) {
78+
duplicates = []
79+
for (const dupe of report.duplicates) {
80+
if (isInChanges(changes, dupe)) {
81+
duplicates.push(dupe)
82+
}
83+
}
84+
return duplicates
85+
}
86+
87+
function formatDuplicates(duplicates, commitHash, repoName) {
88+
const baseUrl = `https://github.com/${repoName}`
89+
return duplicates.map((dupe) => {
90+
return {
91+
first: formUrl(dupe.firstFile, commitHash),
92+
second: formUrl(dupe.secondFile, commitHash),
93+
numberOfLines: dupe.lines,
94+
}
95+
})
96+
function formUrl(file, commitHash) {
97+
return `${baseUrl}/blob/${commitHash}/${file.name}#L${file.start}-L${file.end}`
98+
}
99+
}
100+
101+
async function run() {
102+
const rawDiffPath = process.argv[3]
103+
const jscpdReportPath = process.argv[4]
104+
const commitHash = process.argv[5]
105+
const repoName = process.argv[6]
106+
const changes = await parseDiff(rawDiffPath)
107+
const jscpdReport = JSON.parse(await fs.readFile(jscpdReportPath, 'utf8'))
108+
const filteredDuplicates = filterDuplicates(jscpdReport, changes)
109+
110+
console.log('%s files changes', changes.size)
111+
console.log('%s duplicates found', filteredDuplicates.length)
112+
if (filteredDuplicates.length > 0) {
113+
console.log(formatDuplicates(filteredDuplicates, commitHash, repoName))
114+
console.log(
115+
'* Hint: if these duplicates appear unrelated to the changes, rebase onto or merge in the latest target branch.'
116+
)
117+
process.exit(1)
118+
}
119+
}
120+
121+
/**
122+
* Mini-test Suite
123+
*/
124+
const testDiffFile = path.resolve(__dirname, 'test/test_diff.txt')
125+
let testCounter = 0
126+
function assertEqual(actual, expected) {
127+
if (actual !== expected) {
128+
throw new Error(`Expected ${expected} but got ${actual}`)
129+
}
130+
testCounter += 1
131+
}
132+
133+
async function test() {
134+
test_parseDiffFilePath()
135+
test_parseDiffRange()
136+
test_doesOverlap()
137+
await test_parseDiff()
138+
await test_isCloneInChanges()
139+
await test_isInChanges()
140+
await test_filterDuplicates()
141+
console.log('All tests passed (%s)', testCounter)
142+
}
143+
144+
function test_parseDiffFilePath() {
145+
assertEqual(
146+
parseDiffFilePath(
147+
'diff --git a/.github/workflows/copyPasteDetection.yml b/.github/workflows/copyPasteDetection.yml'
148+
),
149+
'.github/workflows/copyPasteDetection.yml'
150+
)
151+
assertEqual(
152+
parseDiffFilePath('diff --git a/.github/workflows/filterDuplicates.js b/.github/workflows/filterDuplicates.js'),
153+
'.github/workflows/filterDuplicates.js'
154+
)
155+
}
156+
157+
function test_parseDiffRange() {
158+
assertEqual(parseDiffRange('@@ -1,4 +1,4 @@').join(','), '1,5')
159+
assertEqual(parseDiffRange('@@ -10,4 +10,4 @@').join(','), '10,14')
160+
assertEqual(parseDiffRange('@@ -10,4 +10,5 @@').join(','), '10,15')
161+
}
162+
163+
function test_doesOverlap() {
164+
assertEqual(doesOverlap([1, 5], [2, 4]), true)
165+
assertEqual(doesOverlap([2, 3], [2, 4]), true)
166+
assertEqual(doesOverlap([2, 3], [1, 4]), true)
167+
assertEqual(doesOverlap([1, 5], [5, 6]), true)
168+
assertEqual(doesOverlap([1, 5], [6, 7]), false)
169+
assertEqual(doesOverlap([6, 7], [1, 5]), false)
170+
assertEqual(doesOverlap([2, 5], [4, 5]), true)
171+
}
172+
173+
async function test_parseDiff() {
174+
const changes = await parseDiff(testDiffFile)
175+
assertEqual(changes.size, 2)
176+
assertEqual(changes.get('.github/workflows/copyPasteDetection.yml').length, 1)
177+
assertEqual(changes.get('.github/workflows/filterDuplicates.js').length, 1)
178+
assertEqual(changes.get('.github/workflows/filterDuplicates.js')[0].join(','), '1,86')
179+
assertEqual(changes.get('.github/workflows/copyPasteDetection.yml')[0].join(','), '26,73')
180+
}
181+
182+
async function test_isCloneInChanges() {
183+
const changes = await parseDiff(testDiffFile)
184+
assertEqual(
185+
isCloneInChanges(changes, {
186+
name: '.github/workflows/filterDuplicates.js',
187+
start: 1,
188+
end: 86,
189+
}),
190+
true
191+
)
192+
assertEqual(
193+
isCloneInChanges(changes, {
194+
name: '.github/workflows/filterDuplicates.js',
195+
start: 80,
196+
end: 95,
197+
}),
198+
true
199+
)
200+
assertEqual(
201+
isCloneInChanges(changes, {
202+
name: '.github/workflows/filterDuplicates.js',
203+
start: 87,
204+
end: 95,
205+
}),
206+
false
207+
)
208+
assertEqual(
209+
isCloneInChanges(changes, {
210+
name: 'some-fake-file',
211+
start: 1,
212+
end: 100,
213+
}),
214+
false
215+
)
216+
}
217+
218+
async function test_isInChanges() {
219+
const changes = await parseDiff(testDiffFile)
220+
const dupe = {
221+
firstFile: {
222+
name: '.github/workflows/filterDuplicates.js',
223+
start: 1,
224+
end: 86,
225+
},
226+
secondFile: {
227+
name: '.github/workflows/filterDuplicates.js',
228+
start: 80,
229+
end: 95,
230+
},
231+
}
232+
assertEqual(isInChanges(changes, dupe), true)
233+
dupe.secondFile.start = 87
234+
assertEqual(isInChanges(changes, dupe), true)
235+
dupe.firstFile.name = 'some-fake-file'
236+
assertEqual(isInChanges(changes, dupe), false)
237+
}
238+
239+
async function test_filterDuplicates() {
240+
assertEqual(
241+
filterDuplicates(
242+
{
243+
duplicates: [
244+
{
245+
firstFile: {
246+
name: '.github/workflows/filterDuplicates.js',
247+
start: 1,
248+
end: 86,
249+
},
250+
secondFile: {
251+
name: '.github/workflows/filterDuplicates.js',
252+
start: 80,
253+
end: 95,
254+
},
255+
},
256+
],
257+
},
258+
await parseDiff(testDiffFile)
259+
).length,
260+
1
261+
)
262+
assertEqual(
263+
filterDuplicates(
264+
{
265+
duplicates: [
266+
{
267+
firstFile: {
268+
name: 'some-other-file',
269+
start: 1,
270+
end: 86,
271+
},
272+
secondFile: {
273+
name: '.github/workflows/filterDuplicates.js',
274+
start: 90,
275+
end: 95,
276+
},
277+
},
278+
],
279+
},
280+
await parseDiff(testDiffFile)
281+
).length,
282+
0
283+
)
284+
}
285+
286+
async function main() {
287+
const mode = process.argv[2]
288+
if (mode === 'run') {
289+
await run()
290+
} else if (mode === 'test') {
291+
await test()
292+
} else {
293+
throw new Error('Invalid mode')
294+
}
295+
}
296+
297+
void main()

.github/workflows/lintcommit.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ Invalid pull request title: \`${title}\`
130130
* scope: lowercase, <30 chars
131131
* subject: must be <100 chars
132132
* documentation: https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#pull-request-title
133+
* Hint: *close and re-open the PR* to re-trigger CI (after fixing the PR title).
133134
`
134135
: `Pull request title matches the [expected format](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#pull-request-title).`
135136

0 commit comments

Comments
 (0)