Skip to content

Commit 143aa06

Browse files
authored
fix(TS): infer definitions correctly (#357)
* fix(npm): call npm registry endpoint to check if .d.ts exists * fix(TS): move included typings check to formatPkg this is done because the whole package isn't passed through to the next steps * chore: update snapshots * chore(TS): add JSDoc * fix(types): always return TS: null * fix(TS): move main detection into formatPkg
1 parent e9e9ca8 commit 143aa06

File tree

7 files changed

+162
-33
lines changed

7 files changed

+162
-33
lines changed

src/__tests__/__snapshots__/config.test.js.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ Object {
179179
"maxObjSize": 450000,
180180
"npmDownloadsEndpoint": "https://api.npmjs.org/downloads",
181181
"npmRegistryEndpoint": "https://replicate.npmjs.com/registry",
182-
"npmRootEndpoint": "https://api.npmjs.org",
182+
"npmRootEndpoint": "https://registry.npmjs.org",
183183
"popularDownloadsRatio": 0.005,
184184
"replicateConcurrency": 10,
185185
"seq": null,

src/__tests__/__snapshots__/formatPkg.test.js.snap

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,12 @@ Are you in trouble? Read through our [contribution guidelines](https://bitbucket
158158
"tags": Object {
159159
"latest": "1.6.1",
160160
},
161+
"types": Object {
162+
"ts": Object {
163+
"dtsMain": "index.d.ts",
164+
"possible": true,
165+
},
166+
},
161167
"version": "1.6.1",
162168
"versions": Object {
163169
"1.0.0": "2017-01-29T23:47:18.021Z",
@@ -286,6 +292,12 @@ Object {
286292
"tags": Object {
287293
"latest": "0.0.4",
288294
},
295+
"types": Object {
296+
"ts": Object {
297+
"dtsMain": "index.d.ts",
298+
"possible": true,
299+
},
300+
},
289301
"version": "0.0.4",
290302
"versions": Object {
291303
"0.0.1": "2017-06-22T16:19:35.102Z",
@@ -401,6 +413,12 @@ Object {
401413
"tags": Object {
402414
"latest": "4.4.2",
403415
},
416+
"types": Object {
417+
"ts": Object {
418+
"dtsMain": "index.d.ts",
419+
"possible": true,
420+
},
421+
},
404422
"version": "4.4.2",
405423
"versions": Object {
406424
"4.4.2": "2019-04-10T11:34:01.895Z",
@@ -478,6 +496,12 @@ index(arr, obj);
478496
"tags": Object {
479497
"latest": "0.0.1",
480498
},
499+
"types": Object {
500+
"ts": Object {
501+
"dtsMain": "index.d.ts",
502+
"possible": true,
503+
},
504+
},
481505
"version": "0.0.1",
482506
"versions": Object {
483507
"0.0.1": "2012-08-31T16:20:14.894Z",
@@ -531,6 +555,12 @@ Object {
531555
"readme": Any<String>,
532556
"repository": null,
533557
"tags": undefined,
558+
"types": Object {
559+
"ts": Object {
560+
"dtsMain": "index.d.ts",
561+
"possible": true,
562+
},
563+
},
534564
"version": "0.0.0",
535565
"versions": Object {},
536566
}

src/__tests__/formatPkg.test.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,61 @@ describe('adds webpack scaffolds', () => {
168168
});
169169
});
170170

171+
describe('adds TypeScript information', () => {
172+
it('adds types if included in the package.json', () => {
173+
expect(
174+
formatPkg({
175+
name: 'xxx',
176+
lastPublisher: { name: 'unknown' },
177+
types: './test.dts',
178+
})
179+
).toEqual(expect.objectContaining({ types: { ts: 'included' } }));
180+
181+
expect(
182+
formatPkg({
183+
name: 'xxx',
184+
lastPublisher: { name: 'unknown' },
185+
typings: './test.dts',
186+
})
187+
).toEqual(expect.objectContaining({ types: { ts: 'included' } }));
188+
});
189+
190+
it('adds types possible if we can find a main file', () => {
191+
expect(
192+
formatPkg({
193+
name: 'xxx',
194+
lastPublisher: { name: 'unknown' },
195+
main: 'main.js',
196+
})
197+
).toEqual(
198+
expect.objectContaining({
199+
types: { ts: { possible: true, dtsMain: 'main.d.ts' } },
200+
})
201+
);
202+
203+
expect(
204+
formatPkg({
205+
name: 'xxx',
206+
lastPublisher: { name: 'unknown' },
207+
})
208+
).toEqual(
209+
expect.objectContaining({
210+
types: { ts: { possible: true, dtsMain: 'index.d.ts' } },
211+
})
212+
);
213+
});
214+
215+
it('gives up when no main is not js', () => {
216+
expect(
217+
formatPkg({
218+
name: 'xxx',
219+
main: 'shell-script.sh',
220+
lastPublisher: { name: 'unknown' },
221+
})
222+
).toEqual(expect.objectContaining({ types: { ts: null } }));
223+
});
224+
});
225+
171226
describe('test getRepositoryInfo', () => {
172227
const getRepositoryInfo = formatPkg.__RewireAPI__.__get__(
173228
'getRepositoryInfo'

src/__tests__/typescript.test.js

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,10 @@ import { validatePackageExists } from '../npm';
55
import { fileExistsInUnpkg } from '../unpkg.js';
66

77
describe('getTypeScriptSupport()', () => {
8-
it('If types or typings are present in pkg.json - return early', async () => {
9-
let typesSupport = await getTypeScriptSupport({
8+
it('If types are already calculated - return early', async () => {
9+
const typesSupport = await getTypeScriptSupport({
1010
name: 'Has Types',
11-
types: './types',
12-
});
13-
14-
expect(typesSupport).toEqual({ types: { ts: 'included' } });
15-
16-
typesSupport = await getTypeScriptSupport({
17-
name: 'Has Types',
18-
typings: './types',
11+
types: { ts: 'included' },
1912
});
2013

2114
expect(typesSupport).toEqual({ types: { ts: 'included' } });
@@ -24,23 +17,32 @@ describe('getTypeScriptSupport()', () => {
2417
describe('without types/typings', () => {
2518
it('Checks for @types/[name]', async () => {
2619
validatePackageExists.mockResolvedValue(true);
27-
const atTypesSupport = await getTypeScriptSupport({ name: 'my-lib' });
20+
const atTypesSupport = await getTypeScriptSupport({
21+
name: 'my-lib',
22+
types: { ts: null },
23+
});
2824
expect(atTypesSupport).toEqual({ types: { ts: '@types/my-lib' } });
2925
});
3026

3127
it('Checks for a d.ts resolved version of main ', async () => {
3228
validatePackageExists.mockResolvedValue(false);
3329
fileExistsInUnpkg.mockResolvedValue(true);
3430

35-
const typesSupport = await getTypeScriptSupport({ name: 'my-lib' });
31+
const typesSupport = await getTypeScriptSupport({
32+
name: 'my-lib',
33+
types: { ts: { possible: true, dtsMain: 'main.d.ts' } },
34+
});
3635
expect(typesSupport).toEqual({ types: { ts: 'included' } });
3736
});
3837

3938
it('Handles not having and TS types', async () => {
4039
validatePackageExists.mockResolvedValue(false);
4140
fileExistsInUnpkg.mockResolvedValue(false);
4241

43-
const typesSupport = await getTypeScriptSupport({ name: 'my-lib' });
42+
const typesSupport = await getTypeScriptSupport({
43+
name: 'my-lib',
44+
types: { ts: null },
45+
});
4446
expect(typesSupport).toEqual({ types: { ts: null } });
4547
});
4648
});

src/config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import ms from 'ms';
55
const defaultConfig = {
66
npmRegistryEndpoint: 'https://replicate.npmjs.com/registry',
77
npmDownloadsEndpoint: 'https://api.npmjs.org/downloads',
8-
npmRootEndpoint: 'https://api.npmjs.org',
8+
npmRootEndpoint: 'https://registry.npmjs.org',
99
jsDelivrHitsEndpoint: 'https://data.jsdelivr.com/v1/stats/packages/month/all',
1010
unpkgRoot: 'https://unpkg.com',
1111
maxObjSize: 450000,

src/formatPkg.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ export default function formatPkg(pkg) {
5151
}
5252
: null;
5353

54+
const types = getTypes(pkg);
55+
5456
const owner = getOwner(repository, lastPublisher, author); // always favor the repository owner
5557
const { computedKeywords, computedMetadata } = getComputedData(cleaned);
5658
const keywords = getKeywords(cleaned);
@@ -92,6 +94,7 @@ export default function formatPkg(pkg) {
9294
lastPublisher,
9395
owners: (cleaned.owners || []).map(formatUser),
9496
bin: cleaned.bin,
97+
types,
9598
lastCrawl: new Date().toISOString(),
9699
_searchInternal: {
97100
concatenatedName,
@@ -386,3 +389,31 @@ function formatUser(user) {
386389
link: `https://www.npmjs.com/~${encodeURIComponent(user.name)}`,
387390
};
388391
}
392+
393+
function getTypes(pkg) {
394+
// The cheap and simple (+ recommended by TS) way
395+
// of adding a types section to your package.json
396+
if (pkg.types) {
397+
return { ts: 'included' };
398+
}
399+
400+
// Older, but still works way of defining your types
401+
if (pkg.typings) {
402+
return { ts: 'included' };
403+
}
404+
405+
const main = pkg.main || 'index.js';
406+
if (main.endsWith('.js')) {
407+
const dtsMain = main.replace(/js$/, 'd.ts');
408+
return {
409+
ts: {
410+
possible: true,
411+
dtsMain,
412+
},
413+
};
414+
}
415+
416+
return {
417+
ts: null,
418+
};
419+
}

src/typescriptSupport.js

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,24 @@
33
import { validatePackageExists } from './npm.js';
44
import { fileExistsInUnpkg } from './unpkg.js';
55

6+
/**
7+
* @typedef Package
8+
* @property {string} name
9+
* @property {string} version
10+
* @property {{ ts: string | {possible: boolean, dtsMain: string} }} types
11+
*/
12+
613
/**
714
* Basically either
8-
* - { types: { ts: null }} for no existing TypeScript support
9-
* - { types: { ts: "@types/module" }} - for definitely typed support
10-
* - { types: { ts: "included" }} - for types shipped with the module
11-
* */
15+
* - { types: { ts: null }} for no existing TypeScript support
16+
* - { types: { ts: "@types/module" }} - for definitely typed support
17+
* - { types: { ts: "included" }} - for types shipped with the module
18+
* @param {Package} pkg
19+
*/
1220
export async function getTypeScriptSupport(pkg) {
13-
// The cheap and simple (+ recommended by TS) way
14-
// of adding a types section to your package.json
15-
if (pkg.types) {
16-
return { types: { ts: 'included' } };
17-
}
18-
19-
// Older, but still works way of defining your types
20-
if (pkg.typings) {
21-
return { types: { ts: 'included' } };
21+
// Already calculated in `formatPkg`
22+
if (typeof pkg.types.ts === 'string') {
23+
return { types: pkg.types };
2224
}
2325

2426
// The 2nd most likely is definitely typed
@@ -28,11 +30,17 @@ export async function getTypeScriptSupport(pkg) {
2830
return { types: { ts: defTypeName } };
2931
}
3032

31-
// Check if main's JS file can be resolved to a d.ts file instead
32-
const main = pkg.main || 'index.js';
33-
if (main.endsWith('.js')) {
34-
const dtsMain = main.replace(/js$/, 'd.ts');
35-
const resolved = await fileExistsInUnpkg(pkg.name, pkg.version, dtsMain);
33+
if (pkg.types.ts === null) {
34+
return { types: { ts: null } };
35+
}
36+
37+
// Do we have a main .d.ts file?
38+
if (pkg.types.ts.possible === true) {
39+
const resolved = await fileExistsInUnpkg(
40+
pkg.name,
41+
pkg.version,
42+
pkg.types.ts.dtsMain
43+
);
3644
if (resolved) {
3745
return { types: { ts: 'included' } };
3846
}
@@ -41,6 +49,9 @@ export async function getTypeScriptSupport(pkg) {
4149
return { types: { ts: null } };
4250
}
4351

52+
/**
53+
* @param {Array<Package>} pkgs
54+
*/
4455
export function getTSSupport(pkgs) {
4556
return Promise.all(pkgs.map(getTypeScriptSupport));
4657
}

0 commit comments

Comments
 (0)