Skip to content

Commit 136e857

Browse files
authored
Merge branch 'master' into parallel-build
2 parents f732d52 + 6dd3f87 commit 136e857

File tree

482 files changed

+11334
-5802
lines changed

Some content is hidden

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

482 files changed

+11334
-5802
lines changed

.eslintrc.js

Lines changed: 14 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': [
@@ -199,8 +201,20 @@ module.exports = {
199201
name: 'fs',
200202
message: 'Avoid node:fs and use shared/fs/fs.ts when possible.',
201203
},
204+
{
205+
name: 'child_process',
206+
message:
207+
'Avoid child_process, use ChildProcess from `shared/utilities/processUtils.ts` instead.',
208+
},
209+
{
210+
name: '..',
211+
message:
212+
'Avoid importing from index.ts files as it can lead to circular dependencies. Import from the module directly instead.',
213+
},
202214
],
203215
},
204216
],
217+
218+
'prettier/prettier': ['error', { endOfLine: 'auto' }],
205219
},
206220
}

.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: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
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+
process.exit(1)
115+
}
116+
}
117+
118+
/**
119+
* Mini-test Suite
120+
*/
121+
const testDiffFile = path.resolve(__dirname, 'test/test_diff.txt')
122+
let testCounter = 0
123+
function assertEqual(actual, expected) {
124+
if (actual !== expected) {
125+
throw new Error(`Expected ${expected} but got ${actual}`)
126+
}
127+
testCounter += 1
128+
}
129+
130+
async function test() {
131+
test_parseDiffFilePath()
132+
test_parseDiffRange()
133+
test_doesOverlap()
134+
await test_parseDiff()
135+
await test_isCloneInChanges()
136+
await test_isInChanges()
137+
await test_filterDuplicates()
138+
console.log('All tests passed (%s)', testCounter)
139+
}
140+
141+
function test_parseDiffFilePath() {
142+
assertEqual(
143+
parseDiffFilePath(
144+
'diff --git a/.github/workflows/copyPasteDetection.yml b/.github/workflows/copyPasteDetection.yml'
145+
),
146+
'.github/workflows/copyPasteDetection.yml'
147+
)
148+
assertEqual(
149+
parseDiffFilePath('diff --git a/.github/workflows/filterDuplicates.js b/.github/workflows/filterDuplicates.js'),
150+
'.github/workflows/filterDuplicates.js'
151+
)
152+
}
153+
154+
function test_parseDiffRange() {
155+
assertEqual(parseDiffRange('@@ -1,4 +1,4 @@').join(','), '1,5')
156+
assertEqual(parseDiffRange('@@ -10,4 +10,4 @@').join(','), '10,14')
157+
assertEqual(parseDiffRange('@@ -10,4 +10,5 @@').join(','), '10,15')
158+
}
159+
160+
function test_doesOverlap() {
161+
assertEqual(doesOverlap([1, 5], [2, 4]), true)
162+
assertEqual(doesOverlap([2, 3], [2, 4]), true)
163+
assertEqual(doesOverlap([2, 3], [1, 4]), true)
164+
assertEqual(doesOverlap([1, 5], [5, 6]), true)
165+
assertEqual(doesOverlap([1, 5], [6, 7]), false)
166+
assertEqual(doesOverlap([6, 7], [1, 5]), false)
167+
assertEqual(doesOverlap([2, 5], [4, 5]), true)
168+
}
169+
170+
async function test_parseDiff() {
171+
const changes = await parseDiff(testDiffFile)
172+
assertEqual(changes.size, 2)
173+
assertEqual(changes.get('.github/workflows/copyPasteDetection.yml').length, 1)
174+
assertEqual(changes.get('.github/workflows/filterDuplicates.js').length, 1)
175+
assertEqual(changes.get('.github/workflows/filterDuplicates.js')[0].join(','), '1,86')
176+
assertEqual(changes.get('.github/workflows/copyPasteDetection.yml')[0].join(','), '26,73')
177+
}
178+
179+
async function test_isCloneInChanges() {
180+
const changes = await parseDiff(testDiffFile)
181+
assertEqual(
182+
isCloneInChanges(changes, {
183+
name: '.github/workflows/filterDuplicates.js',
184+
start: 1,
185+
end: 86,
186+
}),
187+
true
188+
)
189+
assertEqual(
190+
isCloneInChanges(changes, {
191+
name: '.github/workflows/filterDuplicates.js',
192+
start: 80,
193+
end: 95,
194+
}),
195+
true
196+
)
197+
assertEqual(
198+
isCloneInChanges(changes, {
199+
name: '.github/workflows/filterDuplicates.js',
200+
start: 87,
201+
end: 95,
202+
}),
203+
false
204+
)
205+
assertEqual(
206+
isCloneInChanges(changes, {
207+
name: 'some-fake-file',
208+
start: 1,
209+
end: 100,
210+
}),
211+
false
212+
)
213+
}
214+
215+
async function test_isInChanges() {
216+
const changes = await parseDiff(testDiffFile)
217+
const dupe = {
218+
firstFile: {
219+
name: '.github/workflows/filterDuplicates.js',
220+
start: 1,
221+
end: 86,
222+
},
223+
secondFile: {
224+
name: '.github/workflows/filterDuplicates.js',
225+
start: 80,
226+
end: 95,
227+
},
228+
}
229+
assertEqual(isInChanges(changes, dupe), true)
230+
dupe.secondFile.start = 87
231+
assertEqual(isInChanges(changes, dupe), true)
232+
dupe.firstFile.name = 'some-fake-file'
233+
assertEqual(isInChanges(changes, dupe), false)
234+
}
235+
236+
async function test_filterDuplicates() {
237+
assertEqual(
238+
filterDuplicates(
239+
{
240+
duplicates: [
241+
{
242+
firstFile: {
243+
name: '.github/workflows/filterDuplicates.js',
244+
start: 1,
245+
end: 86,
246+
},
247+
secondFile: {
248+
name: '.github/workflows/filterDuplicates.js',
249+
start: 80,
250+
end: 95,
251+
},
252+
},
253+
],
254+
},
255+
await parseDiff(testDiffFile)
256+
).length,
257+
1
258+
)
259+
assertEqual(
260+
filterDuplicates(
261+
{
262+
duplicates: [
263+
{
264+
firstFile: {
265+
name: 'some-other-file',
266+
start: 1,
267+
end: 86,
268+
},
269+
secondFile: {
270+
name: '.github/workflows/filterDuplicates.js',
271+
start: 90,
272+
end: 95,
273+
},
274+
},
275+
],
276+
},
277+
await parseDiff(testDiffFile)
278+
).length,
279+
0
280+
)
281+
}
282+
283+
async function main() {
284+
const mode = process.argv[2]
285+
if (mode === 'run') {
286+
await run()
287+
} else if (mode === 'test') {
288+
await test()
289+
} else {
290+
throw new Error('Invalid mode')
291+
}
292+
}
293+
294+
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)