Skip to content

Commit efc7aa3

Browse files
authored
Merge pull request #6 from matheusdc/feat/add-usage-details
Add usage details for tokens, functions, objects and JSXElements
2 parents 05405ef + f046b05 commit efc7aa3

File tree

14 files changed

+1977
-1277
lines changed

14 files changed

+1977
-1277
lines changed

.eslintrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,9 @@
99
"env": {
1010
"node": true,
1111
"jest": true
12+
},
13+
"rules": {
14+
"no-unused-vars": "off",
15+
"@typescript-eslint/no-unused-vars": "error"
1216
}
1317
}

README.md

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import { getPackagesUsages } from 'pkg-usage';
2626
const usages: PackageUsage[] = getPackagesUsages({
2727
packages: ['react'],
2828
fileGlobs: `**/**.ts`,
29+
packageJsonCWD: './package.json',
30+
analyzeImportUsages: false,
2931
});
3032

3133
console.log(usages);
@@ -34,11 +36,42 @@ console.log(usages);
3436
Package Usage types
3537

3638
```ts
39+
export type JSXElementUsage = {
40+
line: number;
41+
props: string[];
42+
text: string;
43+
};
44+
45+
export type CallExpressionUsage = {
46+
line: number;
47+
text: string;
48+
};
49+
50+
export type ValueUsage = {
51+
line: number;
52+
text: string;
53+
};
54+
55+
export type PropertyAccessExpressionUsage = {
56+
line: number;
57+
property: string;
58+
text: string;
59+
};
60+
61+
export type Usages =
62+
| JSXElementUsage[]
63+
| (CallExpressionUsage | PropertyAccessExpressionUsage | ValueUsage)[];
64+
65+
export type Import = {
66+
name: string;
67+
type: ExportType;
68+
usages?: Usages;
69+
};
70+
3771
export type FileUsage = {
3872
name: string;
3973
filePath: string;
40-
defaultImport: string | undefined;
41-
namedImports: string[];
74+
imports: Import[];
4275
};
4376

4477
export type PackageUsage = {
@@ -51,11 +84,12 @@ export type PackageUsage = {
5184

5285
## CLI usage
5386

54-
| Options | Description | Example |
55-
| ------------------------ | ------------------------------------ | ---------------------------------------------- |
56-
| -p, --packages | Packages to analyze | -p vue,vuex or --packages="react,redux" |
57-
| -f, --file-globs | Files to analyze based on file globs | -f "_.(ts\|tsx)" or --file-globs="_.(js\|jsx)" |
58-
| -cwd, --package-json-CWD | Directory to start from | -cwd "/path/to/start/from" |
87+
| Options | Description | Example |
88+
| --------------------------- | ------------------------------------- | ---------------------------------------------- |
89+
| -p, --packages | Packages to analyze | -p vue,vuex or --packages="react,redux" |
90+
| -f, --file-globs | Files to analyze based on file globs | -f "_.(ts\|tsx)" or --file-globs="_.(js\|jsx)" |
91+
| -u, --analyze-import-usages | (Experimental) Analyzes import usages | -u |
92+
| -cwd, --package-json-CWD | Directory to start from | -cwd "/path/to/start/from" |
5993

6094
### npx
6195

__tests__/pkg-usage.test.ts

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ beforeEach(() => {
1717
describe('getPackagesUsages()', () => {
1818
describe('given a wrong file glob', () => {
1919
it('should THROW an Error', () => {
20-
const { pkg } = mockPackageUsageFile();
20+
const { pkg } = mockPackageUsageFile({});
2121

2222
expect(() =>
2323
getPackagesUsages({
@@ -30,39 +30,68 @@ describe('getPackagesUsages()', () => {
3030

3131
describe('given a file without import declaration', () => {
3232
it('should return a package usage without a single count', () => {
33-
const { pkg, version } = mockPackageUsageFile(
34-
` file data without import declaration `
35-
);
33+
const { pkg, version } = mockPackageUsageFile({
34+
customData: ` file data without import declaration `,
35+
});
3636

3737
expect(
3838
getPackagesUsages({
3939
packages: [pkg],
40-
fileGlobs: `${MOCKS_DIR}/**.ts`,
40+
fileGlobs: `${MOCKS_DIR}/**.tsx`,
4141
packageJsonCWD: MOCKS_DIR_CWD,
4242
})
4343
).toStrictEqual([{ count: 0, files: [], name: pkg, version }]);
4444
});
4545
});
4646

47-
describe('given any package name and named imports', () => {
47+
describe('given any package name and imports', () => {
4848
it('should return the right package usage', () => {
49-
const { fileName, imports, pkg, version } = mockPackageUsageFile();
49+
const { fileName, imports, pkg, version } = mockPackageUsageFile({});
5050

5151
expect(
5252
getPackagesUsages({
5353
packages: [pkg],
54-
fileGlobs: `${MOCKS_DIR}/**.ts`,
54+
fileGlobs: `${MOCKS_DIR}/**.tsx`,
5555
packageJsonCWD: MOCKS_DIR_CWD,
5656
})
5757
).toStrictEqual([
5858
{
5959
count: 1,
6060
files: [
6161
{
62-
defaultImport: undefined,
63-
filePath: `${MOCKS_DIR_CWD}/${fileName}.ts`,
64-
name: `${fileName}.ts`,
65-
namedImports: imports,
62+
filePath: `${MOCKS_DIR_CWD}/${fileName}.tsx`,
63+
name: `${fileName}.tsx`,
64+
imports,
65+
},
66+
],
67+
name: pkg,
68+
version,
69+
},
70+
]);
71+
});
72+
});
73+
74+
describe('given any package name and imports, and analyzeImportUsages as true', () => {
75+
it('should return the right package usage', () => {
76+
const { fileName, imports, pkg, version } = mockPackageUsageFile({
77+
analyzeImportUsages: true,
78+
});
79+
80+
expect(
81+
getPackagesUsages({
82+
packages: [pkg],
83+
fileGlobs: `${MOCKS_DIR}/**.tsx`,
84+
analyzeImportUsages: true,
85+
packageJsonCWD: MOCKS_DIR_CWD,
86+
})
87+
).toStrictEqual([
88+
{
89+
count: 1,
90+
files: [
91+
{
92+
filePath: `${MOCKS_DIR_CWD}/${fileName}.tsx`,
93+
name: `${fileName}.tsx`,
94+
imports,
6695
},
6796
],
6897
name: pkg,

__tests__/utils.ts

Lines changed: 108 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { join, dirname } from 'path';
22
import { cwd } from 'process';
33
import { writeFileSync, existsSync, mkdirSync, rmdirSync } from 'fs';
44
import f from 'faker';
5+
import { ExportType, Import } from '../src';
56

67
export const MOCKS_DIR = '__mocks__';
78
export const MOCKS_DIR_CWD = join(join(cwd(), MOCKS_DIR));
@@ -30,15 +31,107 @@ function mockUniqueList<T, U>(
3031
.filter((value, index, self) => self.indexOf(value) === index);
3132
}
3233

33-
export function mockPackageUsageFile(customData?: string) {
34-
const imports = mockUniqueList(() => f.hacker.noun().replace(/ |-/g, ''));
34+
function word() {
35+
return f.hacker.noun().replace(/ |-/g, '');
36+
}
37+
38+
function capitalize(word: string) {
39+
return `${word.charAt(0).toUpperCase()}${word.slice(1)}`;
40+
}
41+
42+
function generatePropList(props: string[]) {
43+
return props.map((prop) => `${prop}="${prop}"`).join(' ');
44+
}
45+
46+
function jsxElement(value: string, line: number): [Import, string] {
47+
const props = mockUniqueList(() => word().toLowerCase());
48+
return [
49+
{
50+
name: value,
51+
type: ExportType.named,
52+
usages: [
53+
{
54+
line: line + 2,
55+
props,
56+
text: `<${value} ${generatePropList(props)}/>`,
57+
},
58+
],
59+
},
60+
`function ${capitalize(word())}() { return (<${value} ${generatePropList(
61+
props
62+
)}/>)}`,
63+
];
64+
}
65+
66+
function propertyAccess(value: string, line: number): [Import, string] {
67+
const property = word();
68+
return [
69+
{
70+
name: value,
71+
type: ExportType.named,
72+
usages: [{ line: line + 2, property, text: `\n${value}.${property}` }],
73+
},
74+
`${value}.${property}`,
75+
];
76+
}
77+
78+
function callExpression(value: string, line: number): [Import, string] {
79+
return [
80+
{
81+
name: value,
82+
type: ExportType.named,
83+
usages: [{ line: line + 2, text: `\n${value}()` }],
84+
},
85+
`${value}()`,
86+
];
87+
}
88+
89+
function valueUsage(value: string, line: number): [Import, string] {
90+
return [
91+
{
92+
name: value,
93+
type: ExportType.named,
94+
usages: [{ line: line + 2, text: `\n${value};` }],
95+
},
96+
`${value};`,
97+
];
98+
}
99+
100+
const possibleUsages = [propertyAccess, callExpression, valueUsage, jsxElement];
101+
102+
function mockTSFile(pkgName: string, imports: string[]) {
103+
const importSection = `import { ${imports.join(', ')} } from '${pkgName}';`;
104+
105+
const usages = imports.map((entry, index) =>
106+
possibleUsages[index % possibleUsages.length](entry, index)
107+
);
108+
109+
const mockedImports = usages.map((data) => data[1]);
110+
111+
return {
112+
file: [importSection, ...mockedImports].join('\n'),
113+
imports: usages.map((data) => data[0]),
114+
};
115+
}
116+
117+
type mockPackageUsageFileAttributes = {
118+
customData?: string;
119+
analyzeImportUsages?: boolean;
120+
};
121+
122+
export function mockPackageUsageFile({
123+
customData,
124+
analyzeImportUsages = false,
125+
}: mockPackageUsageFileAttributes) {
126+
const importNames = mockUniqueList(() => capitalize(word()));
35127
const fileName = f.datatype.uuid();
36128
const pkg = f.hacker.noun();
37129
const version = f.system.semver();
38130

131+
const { file, imports } = mockTSFile(pkg, importNames);
39132
// Mocked with random data to generate a resilient test
40-
const data = customData ?? `import { ${imports.join(', ')} } from '${pkg}';`;
41-
writeFile(`${fileName}.ts`, data);
133+
const data = customData ?? file;
134+
writeFile(`${fileName}.tsx`, data);
42135

43136
writeFile(
44137
'package.json',
@@ -54,8 +147,18 @@ export function mockPackageUsageFile(customData?: string) {
54147
})
55148
);
56149

150+
if (customData) {
151+
return {
152+
fileName,
153+
pkg,
154+
version,
155+
};
156+
}
157+
57158
return {
58-
imports,
159+
imports: analyzeImportUsages
160+
? imports
161+
: imports.map(({ name, type }) => ({ name, type })),
59162
fileName,
60163
pkg,
61164
version,

example/bla.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1-
import { apiX, apiB, apiC } from 'bla';
1+
import { apiA, apiB, apiC } from 'bla';
22

3-
const a = bla;
3+
const a = apiA;
4+
5+
console.log(apiB);

example/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"author": "Luis Takahashi",
66
"license": "MIT",
77
"dependencies": {
8-
"bla": "^0.0.1"
8+
"bla": "^0.0.1",
9+
"@scoped/package": "1.5.0"
910
},
1011
"peerDependencies": {
1112
"@scoped/package": "^4.3.5"

example/run-in-ci.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1-
const { getPackagesUsages } = require('../dist');
1+
const { getPackagesUsages } = require('../dist/src/index');
22

33
/**
44
* You could use the result in many use cases example:
55
*
66
* - To fill an S3 with every usage of the desired builds
77
* - To block the CI if the package is not being used
88
*/
9-
const result = getPackagesUsages();
9+
const result = getPackagesUsages({
10+
packages: ['bla', '@scoped/package'],
11+
fileGlobs: './*{.ts,.tsx}',
12+
packageJsonCWD: './package.json',
13+
analyzeImportUsages: true,
14+
});
15+
16+
console.log(JSON.stringify(result, null, 2));

example/something.ts

Lines changed: 0 additions & 2 deletions
This file was deleted.

example/something.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import SomeDefaultImport, { AwesomeStuff, CoolStuff } from '@scoped/package';
2+
import coolApi, { apiD } from 'bla';
3+
4+
export const Comp = () => {
5+
const data = apiD();
6+
7+
coolApi.run();
8+
9+
return (
10+
<>
11+
<AwesomeStuff first={1} second="2" />
12+
<CoolStuff first={2} second="4" />
13+
<CoolStuff second="4" />
14+
<SomeDefaultImport first={1} second="2" third={data}></SomeDefaultImport>
15+
<SomeDefaultImport first={1} third="3" forth={data}></SomeDefaultImport>
16+
<SomeDefaultImport second={1}></SomeDefaultImport>
17+
</>
18+
);
19+
};

scripts/pkg-usage.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env node
22

33
const { getPackagesUsages } = require('pkg-usage');
4-
// const { getPackagesUsages } = require('../dist/src');
4+
55
const { Command } = require('commander');
66

77
const program = new Command();
@@ -24,10 +24,15 @@ program
2424
'-cwd, --package-json-CWD <value>',
2525
'Directory to start from ex: -cwd "/path/to/start/from"'
2626
)
27-
.action(({ packages, fileGlobs, packageJsonCWD }) => {
27+
.option(
28+
'-u, --analyze-import-usages',
29+
'Experimental feature that analyzes usages of imported JSXElements, function calls, object properties, and value usages'
30+
)
31+
.action(({ packages, fileGlobs, analyzeImportUsages, packageJsonCWD }) => {
2832
const usages = getPackagesUsages({
2933
packages,
3034
fileGlobs,
35+
analyzeImportUsages,
3136
packageJsonCWD,
3237
});
3338

0 commit comments

Comments
 (0)