This repository has been archived by the owner on Aug 7, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 72
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: validate imports * disable codecov comments
- Loading branch information
Showing
4 changed files
with
279 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
/* eslint-env jest */ | ||
import { matchFromImports, matchImports } from './syntax'; | ||
jest.mock('threads/worker') | ||
|
||
|
||
describe('validate imports', () => { | ||
it('match imports', () => { | ||
const imports = matchImports(` | ||
import a | ||
import b, c | ||
def import_d(): | ||
import d | ||
def import_e(): import e | ||
import f.f2 | ||
`); | ||
|
||
return expect(imports).toEqual(new Set([ | ||
'a', | ||
'b', | ||
'c', | ||
'd', | ||
'e', | ||
'f.f2' | ||
])); | ||
}); | ||
|
||
it('match imports with comments', () => { | ||
const imports = matchImports(` | ||
import a # some comment | ||
import b # | ||
import c#touching | ||
import d # some # comment | ||
import e, f # | ||
`); | ||
|
||
return expect(imports).toEqual(new Set([ | ||
'a', | ||
'b', | ||
'c', | ||
'd', | ||
'e', | ||
'f' | ||
])); | ||
}); | ||
|
||
it('match imports with irregular spacing', () => { | ||
const imports = matchImports(` | ||
import a | ||
import b, c | ||
import d , e | ||
import f,g | ||
`); | ||
|
||
return expect(imports).toEqual(new Set([ | ||
'a', | ||
'b', | ||
'c', | ||
'd', | ||
'e', | ||
'f', | ||
'g' | ||
])); | ||
}); | ||
|
||
it('match imports with invalid syntax', () => { | ||
const imports = matchImports(` | ||
import a, | ||
import b,,c | ||
import d. | ||
`); | ||
|
||
return expect(imports).toEqual(new Set()); | ||
}); | ||
|
||
it('match from-imports', () => { | ||
const fromImports = matchFromImports(` | ||
from a import ( # after import | ||
b, # after b | ||
c, # after b | ||
) # after end | ||
from d.d1 import ( | ||
e, | ||
f | ||
) | ||
from g import ( | ||
h,, | ||
i | ||
) | ||
def foo(): from j import ( | ||
k, | ||
l | ||
) | ||
from m import (n, o) | ||
from p import q, r # some comment | ||
`); | ||
|
||
return expect(fromImports).toEqual({ | ||
'a': new Set(['b', 'c']), | ||
'd.d1': new Set(['e', 'f']), | ||
'j': new Set(['k', 'l']), | ||
'm': new Set(['n', 'o']), | ||
'p': new Set(['q', 'r']), | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
const namePattern = '[{base}][{base}0-9]*'.replace(/{base}/g, '_a-zA-Z'); | ||
const modulePattern = '{name}(?:\\.{name})*'.replace(/{name}/g, namePattern); | ||
|
||
export function funcPattern({ | ||
lineStart, | ||
captureName, | ||
captureArgs | ||
}: { | ||
lineStart: boolean | ||
captureName: boolean | ||
captureArgs: boolean | ||
}) { | ||
let pattern = ' *def +{name} *\\({args}\\) *:'; | ||
|
||
if (lineStart) pattern = '^' + pattern; | ||
|
||
// TODO: refine | ||
const argsPattern = '.*'; | ||
|
||
pattern = pattern.replace( | ||
/{name}/g, | ||
captureName ? `(${namePattern})` : namePattern | ||
); | ||
pattern = pattern.replace( | ||
/{args}/g, | ||
captureArgs ? `(${argsPattern})` : argsPattern | ||
); | ||
|
||
return pattern; | ||
} | ||
|
||
function splitImports(imports: string) { | ||
return new Set(imports.split(',').map((_import) => _import.trim())); | ||
} | ||
|
||
export function matchImports(code: string) { | ||
const pattern = new RegExp( | ||
[ | ||
'^', | ||
'(?:{func})?'.replace( | ||
/{func}/g, | ||
funcPattern({ | ||
lineStart: false, | ||
captureName: false, | ||
captureArgs: false | ||
}) | ||
), | ||
' *import +({module}(?: *, *{module})*)'.replace( | ||
/{module}/g, | ||
modulePattern | ||
), | ||
' *(?:#.*)?', | ||
'$' | ||
].join(''), | ||
'gm' | ||
); | ||
|
||
const imports: Set<string> = new Set(); | ||
for (const match of code.matchAll(pattern)) { | ||
splitImports(match[1]).forEach((_import) => { imports.add(_import); }); | ||
} | ||
|
||
return imports; | ||
} | ||
|
||
export function matchFromImports(code: string) { | ||
const pattern = new RegExp( | ||
[ | ||
'^', | ||
'(?:{func})?'.replace( | ||
/{func}/g, | ||
funcPattern({ | ||
lineStart: false, | ||
captureName: false, | ||
captureArgs: false | ||
}) | ||
), | ||
' *from +({module}) +import'.replace( | ||
/{module}/g, | ||
modulePattern | ||
), | ||
'(?: *\\(([^)]+)\\)| +({name}(?: *, *{name})*))'.replace( | ||
/{name}/g, | ||
namePattern | ||
), | ||
' *(?:#.*)?', | ||
'$' | ||
].join(''), | ||
'gm' | ||
); | ||
|
||
const fromImports: Record<string, Set<string>> = {}; | ||
for (const match of code.matchAll(pattern)) { | ||
let imports: Set<string>; | ||
if (match[3] === undefined) { | ||
// Get imports as string and remove comments. | ||
let importsString = match[2].replace( | ||
/#.*(\r|\n|\r\n|$)/g, | ||
'' | ||
); | ||
|
||
// If imports have a trailing comma, remove it. | ||
importsString = importsString.trim(); | ||
if (importsString.endsWith(',')) { | ||
importsString = importsString.slice(0, -1); | ||
} | ||
|
||
// Split imports by comma. | ||
imports = splitImports(importsString); | ||
|
||
// If any imports are invalid, don't save them. | ||
const importPattern = new RegExp(`^${namePattern}$`, 'gm') | ||
if (imports.has('') || | ||
[...imports].every((_import) => importPattern.test(_import)) | ||
) { | ||
continue; | ||
} | ||
} else { | ||
imports = splitImports(match[3]); | ||
} | ||
|
||
fromImports[match[1]] = imports; | ||
} | ||
|
||
return fromImports; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters