Skip to content

Commit b2f65d7

Browse files
committed
feat: add usage details for tokens, functions, objects and JSXElements
1 parent 996d32c commit b2f65d7

File tree

10 files changed

+1827
-1987
lines changed

10 files changed

+1827
-1987
lines changed

README.md

Lines changed: 35 additions & 2 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+
2931
});
3032

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

3638
```ts
39+
40+
export type JSXElementUsage = {
41+
line: number;
42+
props: string[];
43+
text: string;
44+
};
45+
46+
export type CallExpressionUsage = {
47+
line: number;
48+
text: string;
49+
};
50+
51+
export type ValueUsage = {
52+
line: number;
53+
text: string;
54+
};
55+
56+
export type PropertyAccessExpressionUsage = {
57+
line: number;
58+
property: string;
59+
text: string;
60+
};
61+
62+
export type Usages =
63+
| JSXElementUsage[]
64+
| (CallExpressionUsage | PropertyAccessExpressionUsage | ValueUsage)[];
65+
66+
export type Import = {
67+
name: string;
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 = {

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: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
"author": "Luis Takahashi",
66
"license": "MIT",
77
"dependencies": {
8-
"bla": "^0.0.1"
8+
"bla": "^0.0.1",
9+
"ts-morph": "^14.0.0",
10+
"@scoped/package": "1.5.0"
911
},
1012
"peerDependencies": {
1113
"@scoped/package": "^4.3.5"

example/run-in-ci.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
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+
});
14+
15+
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+
};

src/core.ts

Lines changed: 100 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,102 @@
1-
import { Project, SourceFile } from 'ts-morph';
1+
import {
2+
ImportDeclaration,
3+
Project,
4+
ReferencedSymbol,
5+
SourceFile,
6+
SyntaxKind,
7+
} from 'ts-morph';
28

3-
import { getPackageVersion, nonNullish } from './helpers';
4-
import { FileUsage, PackageUsage, Options } from './types';
9+
import {
10+
getJSXElementProps,
11+
getPackageVersion,
12+
getProperty,
13+
isCallExpression,
14+
isJSXElement,
15+
isPropertyAccessExpression,
16+
isValue,
17+
line,
18+
nonNullish,
19+
print,
20+
} from './helpers';
21+
22+
import {
23+
PackageUsage,
24+
Options,
25+
JSXElementUsage,
26+
Import,
27+
CallExpressionUsage,
28+
PropertyAccessExpressionUsage,
29+
FileUsage,
30+
} from './types';
31+
32+
const getDetailsFromImports = (importDeclaration: ImportDeclaration) => {
33+
const defaultReferences = importDeclaration
34+
.getDefaultImport()
35+
?.findReferences();
36+
37+
const namedReferences = importDeclaration
38+
.getNamedImports()
39+
.map((named) =>
40+
named.getLastChildByKindOrThrow(SyntaxKind.Identifier).findReferences()
41+
);
42+
43+
const propsList = [
44+
...getDetailsFromReferences(defaultReferences),
45+
...namedReferences.map(getDetailsFromReferences).flat(),
46+
];
47+
48+
return propsList;
49+
};
50+
51+
const getDetailsFromReferences = (
52+
references: ReferencedSymbol[] = []
53+
): Import[] =>
54+
references.map((referencedSymbol) => ({
55+
name: referencedSymbol.getDefinition().getNode().getText(),
56+
usages: referencedSymbol
57+
.getReferences()
58+
.flatMap(
59+
(
60+
reference
61+
):
62+
| JSXElementUsage
63+
| CallExpressionUsage
64+
| PropertyAccessExpressionUsage
65+
| never[] => {
66+
if (isValue(reference)) {
67+
return {
68+
line: line(reference),
69+
text: print(reference),
70+
};
71+
}
72+
73+
if (isCallExpression(reference)) {
74+
return {
75+
line: line(reference),
76+
text: print(reference),
77+
};
78+
}
79+
80+
if (isPropertyAccessExpression(reference)) {
81+
return {
82+
line: line(reference),
83+
property: getProperty(reference),
84+
text: print(reference),
85+
};
86+
}
87+
88+
if (isJSXElement(reference)) {
89+
return {
90+
line: line(reference),
91+
props: getJSXElementProps(reference),
92+
text: print(reference),
93+
};
94+
}
95+
96+
return [];
97+
}
98+
),
99+
}));
5100

6101
function getPackageUsage(
7102
pkg: string,
@@ -21,14 +116,12 @@ function getPackageUsage(
21116
return undefined;
22117
}
23118

24-
const namedImports = importDeclaration.getNamedImports();
25-
const defaultImport = importDeclaration.getDefaultImport();
119+
const imports = getDetailsFromImports(importDeclaration);
26120

27121
return {
28122
name: sourceFile.getBaseName(),
29123
filePath: sourceFile.getFilePath(),
30-
defaultImport: defaultImport?.getText(),
31-
namedImports: namedImports.map((a) => a.getName()),
124+
imports,
32125
};
33126
}
34127

src/helpers.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pkgup from 'pkg-up';
22

33
import { readFileSync } from 'fs';
4+
import { ReferenceEntry, SyntaxKind } from 'ts-morph';
45

56
// istanbul ignore next
67
function getPackageJson(packageJsonCWD?: string) {
@@ -38,3 +39,47 @@ export function getPackageVersion(pkg: string, packageJsonCWD?: string) {
3839
export function nonNullish<Value>(v: Value): v is NonNullable<Value> {
3940
return v !== undefined && v !== null;
4041
}
42+
43+
export const getJSXElementProps = (reference: ReferenceEntry) =>
44+
reference
45+
.getNode()
46+
.getParentOrThrow()
47+
.getFirstChildByKindOrThrow(SyntaxKind.JsxAttributes)
48+
.getChildrenOfKind(SyntaxKind.JsxAttribute)
49+
.map((attribute) =>
50+
attribute.getFirstChildByKindOrThrow(SyntaxKind.Identifier).getText()
51+
);
52+
53+
export const getProperty = (reference: ReferenceEntry) =>
54+
reference
55+
.getNode()
56+
.getParentOrThrow()
57+
.getLastChildByKindOrThrow(SyntaxKind.Identifier)
58+
.getText();
59+
60+
export const isJSXElement = (reference: ReferenceEntry) =>
61+
reference.getNode().getParentOrThrow().getKind() ===
62+
SyntaxKind.JsxOpeningElement ||
63+
reference.getNode().getParentOrThrow().getKind() ===
64+
SyntaxKind.JsxSelfClosingElement;
65+
66+
export const isValue = (reference: ReferenceEntry) =>
67+
reference.getNode().getParentOrThrow().getKind() ===
68+
SyntaxKind.VariableDeclaration ||
69+
reference.getNode().getParentOrThrow().getKind() ===
70+
SyntaxKind.ExpressionStatement ||
71+
reference.getNode().getParentOrThrow().getKind() === SyntaxKind.SyntaxList;
72+
73+
export const isCallExpression = (reference: ReferenceEntry) =>
74+
reference.getNode().getParentOrThrow().getKind() ===
75+
SyntaxKind.CallExpression;
76+
77+
export const isPropertyAccessExpression = (reference: ReferenceEntry) =>
78+
reference.getNode().getParentOrThrow().getKind() ===
79+
SyntaxKind.PropertyAccessExpression;
80+
81+
export const print = (reference: ReferenceEntry) =>
82+
reference.getNode().getParentOrThrow().getFullText();
83+
84+
export const line = (reference: ReferenceEntry) =>
85+
reference.getNode().getStartLineNumber();

src/types.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,16 @@ export type Options = {
77
export type FileUsage = {
88
name: string;
99
filePath: string;
10-
defaultImport: string | undefined;
11-
namedImports: string[];
10+
imports: Import[];
11+
};
12+
13+
export type Usages =
14+
| JSXElementUsage[]
15+
| (CallExpressionUsage | PropertyAccessExpressionUsage | ValueUsage)[];
16+
17+
export type Import = {
18+
name: string;
19+
usages: Usages;
1220
};
1321

1422
export type PackageUsage = {
@@ -17,3 +25,25 @@ export type PackageUsage = {
1725
files?: FileUsage[];
1826
version?: string;
1927
};
28+
29+
export type JSXElementUsage = {
30+
line: number;
31+
props: string[];
32+
text: string;
33+
};
34+
35+
export type CallExpressionUsage = {
36+
line: number;
37+
text: string;
38+
};
39+
40+
export type ValueUsage = {
41+
line: number;
42+
text: string;
43+
};
44+
45+
export type PropertyAccessExpressionUsage = {
46+
line: number;
47+
property: string;
48+
text: string;
49+
};

0 commit comments

Comments
 (0)