Skip to content

Commit

Permalink
fix(TS): infer definitions correctly (#357)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Haroenv committed Jul 8, 2019
1 parent e9e9ca8 commit 143aa06
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 33 deletions.
2 changes: 1 addition & 1 deletion src/__tests__/__snapshots__/config.test.js.snap
Expand Up @@ -179,7 +179,7 @@ Object {
"maxObjSize": 450000,
"npmDownloadsEndpoint": "https://api.npmjs.org/downloads",
"npmRegistryEndpoint": "https://replicate.npmjs.com/registry",
"npmRootEndpoint": "https://api.npmjs.org",
"npmRootEndpoint": "https://registry.npmjs.org",
"popularDownloadsRatio": 0.005,
"replicateConcurrency": 10,
"seq": null,
Expand Down
30 changes: 30 additions & 0 deletions src/__tests__/__snapshots__/formatPkg.test.js.snap
Expand Up @@ -158,6 +158,12 @@ Are you in trouble? Read through our [contribution guidelines](https://bitbucket
"tags": Object {
"latest": "1.6.1",
},
"types": Object {
"ts": Object {
"dtsMain": "index.d.ts",
"possible": true,
},
},
"version": "1.6.1",
"versions": Object {
"1.0.0": "2017-01-29T23:47:18.021Z",
Expand Down Expand Up @@ -286,6 +292,12 @@ Object {
"tags": Object {
"latest": "0.0.4",
},
"types": Object {
"ts": Object {
"dtsMain": "index.d.ts",
"possible": true,
},
},
"version": "0.0.4",
"versions": Object {
"0.0.1": "2017-06-22T16:19:35.102Z",
Expand Down Expand Up @@ -401,6 +413,12 @@ Object {
"tags": Object {
"latest": "4.4.2",
},
"types": Object {
"ts": Object {
"dtsMain": "index.d.ts",
"possible": true,
},
},
"version": "4.4.2",
"versions": Object {
"4.4.2": "2019-04-10T11:34:01.895Z",
Expand Down Expand Up @@ -478,6 +496,12 @@ index(arr, obj);
"tags": Object {
"latest": "0.0.1",
},
"types": Object {
"ts": Object {
"dtsMain": "index.d.ts",
"possible": true,
},
},
"version": "0.0.1",
"versions": Object {
"0.0.1": "2012-08-31T16:20:14.894Z",
Expand Down Expand Up @@ -531,6 +555,12 @@ Object {
"readme": Any<String>,
"repository": null,
"tags": undefined,
"types": Object {
"ts": Object {
"dtsMain": "index.d.ts",
"possible": true,
},
},
"version": "0.0.0",
"versions": Object {},
}
Expand Down
55 changes: 55 additions & 0 deletions src/__tests__/formatPkg.test.js
Expand Up @@ -168,6 +168,61 @@ describe('adds webpack scaffolds', () => {
});
});

describe('adds TypeScript information', () => {
it('adds types if included in the package.json', () => {
expect(
formatPkg({
name: 'xxx',
lastPublisher: { name: 'unknown' },
types: './test.dts',
})
).toEqual(expect.objectContaining({ types: { ts: 'included' } }));

expect(
formatPkg({
name: 'xxx',
lastPublisher: { name: 'unknown' },
typings: './test.dts',
})
).toEqual(expect.objectContaining({ types: { ts: 'included' } }));
});

it('adds types possible if we can find a main file', () => {
expect(
formatPkg({
name: 'xxx',
lastPublisher: { name: 'unknown' },
main: 'main.js',
})
).toEqual(
expect.objectContaining({
types: { ts: { possible: true, dtsMain: 'main.d.ts' } },
})
);

expect(
formatPkg({
name: 'xxx',
lastPublisher: { name: 'unknown' },
})
).toEqual(
expect.objectContaining({
types: { ts: { possible: true, dtsMain: 'index.d.ts' } },
})
);
});

it('gives up when no main is not js', () => {
expect(
formatPkg({
name: 'xxx',
main: 'shell-script.sh',
lastPublisher: { name: 'unknown' },
})
).toEqual(expect.objectContaining({ types: { ts: null } }));
});
});

describe('test getRepositoryInfo', () => {
const getRepositoryInfo = formatPkg.__RewireAPI__.__get__(
'getRepositoryInfo'
Expand Down
28 changes: 15 additions & 13 deletions src/__tests__/typescript.test.js
Expand Up @@ -5,17 +5,10 @@ import { validatePackageExists } from '../npm';
import { fileExistsInUnpkg } from '../unpkg.js';

describe('getTypeScriptSupport()', () => {
it('If types or typings are present in pkg.json - return early', async () => {
let typesSupport = await getTypeScriptSupport({
it('If types are already calculated - return early', async () => {
const typesSupport = await getTypeScriptSupport({
name: 'Has Types',
types: './types',
});

expect(typesSupport).toEqual({ types: { ts: 'included' } });

typesSupport = await getTypeScriptSupport({
name: 'Has Types',
typings: './types',
types: { ts: 'included' },
});

expect(typesSupport).toEqual({ types: { ts: 'included' } });
Expand All @@ -24,23 +17,32 @@ describe('getTypeScriptSupport()', () => {
describe('without types/typings', () => {
it('Checks for @types/[name]', async () => {
validatePackageExists.mockResolvedValue(true);
const atTypesSupport = await getTypeScriptSupport({ name: 'my-lib' });
const atTypesSupport = await getTypeScriptSupport({
name: 'my-lib',
types: { ts: null },
});
expect(atTypesSupport).toEqual({ types: { ts: '@types/my-lib' } });
});

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

const typesSupport = await getTypeScriptSupport({ name: 'my-lib' });
const typesSupport = await getTypeScriptSupport({
name: 'my-lib',
types: { ts: { possible: true, dtsMain: 'main.d.ts' } },
});
expect(typesSupport).toEqual({ types: { ts: 'included' } });
});

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

const typesSupport = await getTypeScriptSupport({ name: 'my-lib' });
const typesSupport = await getTypeScriptSupport({
name: 'my-lib',
types: { ts: null },
});
expect(typesSupport).toEqual({ types: { ts: null } });
});
});
Expand Down
2 changes: 1 addition & 1 deletion src/config.js
Expand Up @@ -5,7 +5,7 @@ import ms from 'ms';
const defaultConfig = {
npmRegistryEndpoint: 'https://replicate.npmjs.com/registry',
npmDownloadsEndpoint: 'https://api.npmjs.org/downloads',
npmRootEndpoint: 'https://api.npmjs.org',
npmRootEndpoint: 'https://registry.npmjs.org',
jsDelivrHitsEndpoint: 'https://data.jsdelivr.com/v1/stats/packages/month/all',
unpkgRoot: 'https://unpkg.com',
maxObjSize: 450000,
Expand Down
31 changes: 31 additions & 0 deletions src/formatPkg.js
Expand Up @@ -51,6 +51,8 @@ export default function formatPkg(pkg) {
}
: null;

const types = getTypes(pkg);

const owner = getOwner(repository, lastPublisher, author); // always favor the repository owner
const { computedKeywords, computedMetadata } = getComputedData(cleaned);
const keywords = getKeywords(cleaned);
Expand Down Expand Up @@ -92,6 +94,7 @@ export default function formatPkg(pkg) {
lastPublisher,
owners: (cleaned.owners || []).map(formatUser),
bin: cleaned.bin,
types,
lastCrawl: new Date().toISOString(),
_searchInternal: {
concatenatedName,
Expand Down Expand Up @@ -386,3 +389,31 @@ function formatUser(user) {
link: `https://www.npmjs.com/~${encodeURIComponent(user.name)}`,
};
}

function getTypes(pkg) {
// The cheap and simple (+ recommended by TS) way
// of adding a types section to your package.json
if (pkg.types) {
return { ts: 'included' };
}

// Older, but still works way of defining your types
if (pkg.typings) {
return { ts: 'included' };
}

const main = pkg.main || 'index.js';
if (main.endsWith('.js')) {
const dtsMain = main.replace(/js$/, 'd.ts');
return {
ts: {
possible: true,
dtsMain,
},
};
}

return {
ts: null,
};
}
47 changes: 29 additions & 18 deletions src/typescriptSupport.js
Expand Up @@ -3,22 +3,24 @@
import { validatePackageExists } from './npm.js';
import { fileExistsInUnpkg } from './unpkg.js';

/**
* @typedef Package
* @property {string} name
* @property {string} version
* @property {{ ts: string | {possible: boolean, dtsMain: string} }} types
*/

/**
* Basically either
* - { types: { ts: null }} for no existing TypeScript support
* - { types: { ts: "@types/module" }} - for definitely typed support
* - { types: { ts: "included" }} - for types shipped with the module
* */
* - { types: { ts: null }} for no existing TypeScript support
* - { types: { ts: "@types/module" }} - for definitely typed support
* - { types: { ts: "included" }} - for types shipped with the module
* @param {Package} pkg
*/
export async function getTypeScriptSupport(pkg) {
// The cheap and simple (+ recommended by TS) way
// of adding a types section to your package.json
if (pkg.types) {
return { types: { ts: 'included' } };
}

// Older, but still works way of defining your types
if (pkg.typings) {
return { types: { ts: 'included' } };
// Already calculated in `formatPkg`
if (typeof pkg.types.ts === 'string') {
return { types: pkg.types };
}

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

// Check if main's JS file can be resolved to a d.ts file instead
const main = pkg.main || 'index.js';
if (main.endsWith('.js')) {
const dtsMain = main.replace(/js$/, 'd.ts');
const resolved = await fileExistsInUnpkg(pkg.name, pkg.version, dtsMain);
if (pkg.types.ts === null) {
return { types: { ts: null } };
}

// Do we have a main .d.ts file?
if (pkg.types.ts.possible === true) {
const resolved = await fileExistsInUnpkg(
pkg.name,
pkg.version,
pkg.types.ts.dtsMain
);
if (resolved) {
return { types: { ts: 'included' } };
}
Expand All @@ -41,6 +49,9 @@ export async function getTypeScriptSupport(pkg) {
return { types: { ts: null } };
}

/**
* @param {Array<Package>} pkgs
*/
export function getTSSupport(pkgs) {
return Promise.all(pkgs.map(getTypeScriptSupport));
}

0 comments on commit 143aa06

Please sign in to comment.