Skip to content

Commit 1d58008

Browse files
committed
feat(analyze): findRequires
Signed-off-by: Lexus Drumgold <unicornware@flexdevelopment.llc>
1 parent 0dc6d7a commit 1d58008

File tree

5 files changed

+268
-0
lines changed

5 files changed

+268
-0
lines changed

src/constants.ts

+8
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ export const EXPORT_NAMED_REGEX: RegExp =
5555
export const EXPORT_STAR_REGEX: RegExp =
5656
/(?<!(?:\/\/|\*).*)\bexport\s*\*(?:\s*as\s+(?<name>[\w$]+)\s+)?\s*(?:\s*from\s*["']\s*(?<specifier>(?<="\s*)[^"]*[^\s"](?=\s*")|(?<='\s*)[^']*[^\s'](?=\s*'))\s*["'][^\n;]*)?/gm
5757

58+
/**
59+
* Require statement regex.
60+
*
61+
* @const {RegExp} REQUIRE_STATEMENT_REGEX
62+
*/
63+
export const REQUIRE_STATEMENT_REGEX: RegExp =
64+
/(?<!(?:\/\/|\*).*)(?:\bconst[ {]+(?<imports>[\w\t\n\r $*,/]+)[ =}]+)?(?<type>\brequire(?:\.resolve)?)\(["'](?<specifier>[\w./-]+)["']\)/gm
65+
5866
/**
5967
* Static import statement regex.
6068
*
+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Vitest Snapshot v1
2+
3+
exports[`unit:lib/findRequires > require > should find require 1`] = `
4+
[
5+
{
6+
"code": "require(\\"./lib\\")",
7+
"end": 16,
8+
"imports": [],
9+
"specifier": "./lib",
10+
"start": 0,
11+
"type": "require",
12+
},
13+
]
14+
`;
15+
16+
exports[`unit:lib/findRequires > require > should find require declaration 1`] = `
17+
[
18+
{
19+
"code": "const lib = require(\\"./lib\\")",
20+
"end": 28,
21+
"imports": [
22+
"default",
23+
],
24+
"specifier": "./lib",
25+
"start": 0,
26+
"type": "require",
27+
},
28+
]
29+
`;
30+
31+
exports[`unit:lib/findRequires > require > should find require with named imports 1`] = `
32+
[
33+
{
34+
"code": "const { addFive } = require(\\"./lib\\")",
35+
"end": 36,
36+
"imports": [
37+
"addFive",
38+
],
39+
"specifier": "./lib",
40+
"start": 0,
41+
"type": "require",
42+
},
43+
]
44+
`;
45+
46+
exports[`unit:lib/findRequires > require > should find require with named imports in multi-line statement 1`] = `
47+
[
48+
{
49+
"code": "const {
50+
addFive,
51+
addFour,
52+
addThree,
53+
addTwo,
54+
squareFive,
55+
squareFour,
56+
squareThree,
57+
squareTwo
58+
} = require('./lib')",
59+
"end": 127,
60+
"imports": [
61+
"addFive",
62+
"addFour",
63+
"addThree",
64+
"addTwo",
65+
"squareFive",
66+
"squareFour",
67+
"squareThree",
68+
"squareTwo",
69+
],
70+
"specifier": "./lib",
71+
"start": 0,
72+
"type": "require",
73+
},
74+
]
75+
`;
76+
77+
exports[`unit:lib/findRequires > require.resolve > should find require.resolve 1`] = `
78+
[
79+
{
80+
"code": "require.resolve(\\"tsconfig-loader\\")",
81+
"end": 34,
82+
"imports": [],
83+
"specifier": "tsconfig-loader",
84+
"start": 0,
85+
"type": "require.resolve",
86+
},
87+
]
88+
`;
89+
90+
exports[`unit:lib/findRequires > require.resolve > should find require.resolve declaration 1`] = `
91+
[
92+
{
93+
"code": "const loader = require.resolve(\\"tsconfig-loader\\")",
94+
"end": 49,
95+
"imports": [],
96+
"specifier": "tsconfig-loader",
97+
"start": 0,
98+
"type": "require.resolve",
99+
},
100+
]
101+
`;
+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/**
2+
* @file Unit Tests - findRequires
3+
* @module mlly/lib/tests/findRequires/unit
4+
*/
5+
6+
import dedent from 'ts-dedent'
7+
import testSubject from '../find-requires'
8+
9+
describe('unit:lib/findRequires', () => {
10+
describe('require', () => {
11+
it('should find require', () => {
12+
// Act
13+
const results = testSubject('require("./lib")')
14+
15+
// Expect
16+
expect(results).to.be.an('array').of.length(1)
17+
expect(results).toMatchSnapshot()
18+
})
19+
20+
it('should find require declaration', () => {
21+
// Act
22+
const results = testSubject('const lib = require("./lib")')
23+
24+
// Expect
25+
expect(results).to.be.an('array').of.length(1)
26+
expect(results).toMatchSnapshot()
27+
})
28+
29+
it('should find require with named imports', () => {
30+
// Act
31+
const results = testSubject('const { addFive } = require("./lib")')
32+
33+
// Expect
34+
expect(results).to.be.an('array').of.length(1)
35+
expect(results).toMatchSnapshot()
36+
})
37+
38+
it('should find require with named imports in multi-line statement', () => {
39+
// Arrange
40+
const code = dedent`
41+
const {
42+
addFive,
43+
addFour,
44+
addThree,
45+
addTwo,
46+
squareFive,
47+
squareFour,
48+
squareThree,
49+
squareTwo
50+
} = require('./lib')
51+
`
52+
53+
// Act
54+
const results = testSubject(code)
55+
56+
// Expect
57+
expect(results).to.be.an('array').of.length(1)
58+
expect(results).toMatchSnapshot()
59+
})
60+
61+
it('should ignore require in multi-line comment', () => {
62+
// Arrange
63+
const code = dedent`
64+
/**
65+
* @example
66+
* require('foo')
67+
*/
68+
`
69+
70+
// Act + Expect
71+
expect(testSubject(code)).to.be.an('array').of.length(0)
72+
})
73+
74+
it('should ignore require in single-line comment', () => {
75+
expect(testSubject('// require("foo")')).to.be.an('array').of.length(0)
76+
})
77+
})
78+
79+
describe('require.resolve', () => {
80+
it('should find require.resolve', () => {
81+
// Act
82+
const results = testSubject('require.resolve("tsconfig-loader")')
83+
84+
// Expect
85+
expect(results).to.be.an('array').of.length(1)
86+
expect(results).toMatchSnapshot()
87+
})
88+
89+
it('should find require.resolve declaration', () => {
90+
// Arrange
91+
const code = 'const loader = require.resolve("tsconfig-loader")'
92+
93+
// Act
94+
const results = testSubject(code)
95+
96+
// Expect
97+
expect(results).to.be.an('array').of.length(1)
98+
expect(results).toMatchSnapshot()
99+
})
100+
101+
it('should ignore require.resolve in multi-line comment', () => {
102+
// Arrange
103+
const code = dedent`
104+
/**
105+
* @example
106+
* require.resolve('tsconfig-loader')
107+
*/
108+
`
109+
110+
// Act + Expect
111+
expect(testSubject(code)).to.be.an('array').of.length(0)
112+
})
113+
114+
it('should ignore require.resolve in single-line comment', () => {
115+
// Arrange
116+
const code = '// require.resolve("tsconfig-loader")'
117+
118+
// Act + Expect
119+
expect(testSubject(code)).to.be.an('array').of.length(0)
120+
})
121+
})
122+
})

src/lib/find-requires.ts

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* @file findRequires
3+
* @module mlly/lib/findRequires
4+
*/
5+
6+
import { REQUIRE_STATEMENT_REGEX } from '#src/constants'
7+
import type { RequireStatement } from '#src/interfaces'
8+
9+
/**
10+
* Finds all `require` and `require.resolve` statements in `code`.
11+
*
12+
* @param {string} code - Code to check
13+
* @return {RequireStatement[]} Require statement objects
14+
*/
15+
const findRequires = (code: string): RequireStatement[] => {
16+
return [...code.matchAll(REQUIRE_STATEMENT_REGEX)].map(match => {
17+
const { 0: statement = '', index: start = 0, groups = {} } = match
18+
const { imports = '', specifier = '', type = '' } = groups
19+
20+
return {
21+
code: statement,
22+
end: start + statement.length,
23+
imports:
24+
imports === '' || type === 'require.resolve'
25+
? []
26+
: /const *\w/.test(statement)
27+
? ['default']
28+
: imports.split(',').map(name => name.trim()),
29+
specifier,
30+
start,
31+
type: type as RequireStatement['type']
32+
}
33+
})
34+
}
35+
36+
export default findRequires

src/lib/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
export { default as detectSyntax } from './detect-syntax'
77
export { default as findDynamicImports } from './find-dynamic-imports'
88
export { default as findExports } from './find-exports'
9+
export { default as findRequires } from './find-requires'
910
export { default as findStaticImports } from './find-static-imports'
1011
export { default as hasCJSSyntax } from './has-cjs-syntax'
1112
export { default as hasESMSyntax } from './has-esm-syntax'

0 commit comments

Comments
 (0)