Skip to content

Commit

Permalink
feat(compartment-mapper): Use package.json files in nested folders of…
Browse files Browse the repository at this point in the history
… a package when determining module type
  • Loading branch information
naugtur committed May 16, 2022
1 parent 345ea24 commit 4b1c6f4
Show file tree
Hide file tree
Showing 12 changed files with 472 additions and 23 deletions.
17 changes: 17 additions & 0 deletions packages/compartment-mapper/src/node-modules.js
Expand Up @@ -35,6 +35,7 @@
*/

import { inferExports } from './infer-exports.js';
import { searchDescriptor } from './search.js';
import { parseLocatedJson } from './json.js';
import { unpackReadPowers } from './powers.js';
import { pathCompare } from './compartment-map.js';
Expand Down Expand Up @@ -277,6 +278,13 @@ const graphPackage = async (
/** @type {Record<string, Language>} */
const types = {};

const readDescriptorUpwards = async path => {
const location = resolveLocation(path, packageLocation);
// readDescriptor coming from above is memoized, so this is not awfully slow
const { data } = await searchDescriptor(location, readDescriptor);
return data;
};

Object.assign(result, {
name,
path: undefined,
Expand All @@ -288,6 +296,15 @@ const graphPackage = async (
parsers: inferParsers(packageDescriptor, packageLocation),
});

await Promise.all(
values(result.exports).map(async item => {
const descriptor = await readDescriptorUpwards(item);
if (descriptor && descriptor.type === 'module') {
types[item] = 'mjs';
}
}),
);

await Promise.all(children);
return undefined;
};
Expand Down
97 changes: 74 additions & 23 deletions packages/compartment-mapper/src/search.js
Expand Up @@ -18,47 +18,98 @@ const decoder = new TextDecoder();
const resolveLocation = (rel, abs) => new URL(rel, abs).toString();

/**
* Searches for the first ancestor directory of a module file that contains a
* Searches for the first ancestor directory of given location that contains a
* package.json.
* Probes by attempting to read the file, not stat.
* To avoid duplicate work later, returns the text of the package.json for
* inevitable later use.
* Probes by calling readDescriptor.
* Returns the result of readDescriptor as data whenever a value is returned.
*
* @param {ReadFn} read
* @param {string} moduleLocation
* @returns {Promise<{
* packageLocation: string,
* packageDescriptorLocation: string,
* packageDescriptorText: string,
* moduleSpecifier: string,
* }>}
* @template T
* @param {string} location
* @param {(location:string)=>Promise<T>} readDescriptor
* @returns {Promise<{data:T, directory: string, location:string, packageDescriptorLocation: string}>}
*/
export const search = async (read, moduleLocation) => {
let directory = resolveLocation('./', moduleLocation);
export const searchDescriptor = async (location, readDescriptor) => {
let directory = resolveLocation('./', location);
for (;;) {
const packageDescriptorLocation = resolveLocation(
'package.json',
directory,
);
// eslint-disable-next-line no-await-in-loop
const packageDescriptorBytes = await read(packageDescriptorLocation).catch(
const data = await readDescriptor(packageDescriptorLocation).catch(
() => undefined,
);
if (packageDescriptorBytes !== undefined) {
const packageDescriptorText = decoder.decode(packageDescriptorBytes);
if (data !== undefined) {
return {
packageLocation: directory,
packageDescriptorText,
data,
directory,
location,
packageDescriptorLocation,
moduleSpecifier: relativize(relative(directory, moduleLocation)),
};
}
const parentDirectory = resolveLocation('../', directory);
if (parentDirectory === directory) {
throw new Error(
`Cannot find package.json along path to module ${q(moduleLocation)}`,
);
throw new Error(`Cannot find package.json along path to ${q(location)}`);
}
directory = parentDirectory;
}
};

/**
* @param {ReadFn} read
* @param {string} packageDescriptorLocation
* @returns {Promise<string | undefined>}
*/
const readDescriptorDefault = async (
read,
packageDescriptorLocation,
// eslint-disable-next-line consistent-return
) => {
const packageDescriptorBytes = await read(packageDescriptorLocation).catch(
() => undefined,
);
if (packageDescriptorBytes !== undefined) {
const packageDescriptorText = decoder.decode(packageDescriptorBytes);
return packageDescriptorText;
}
};

/**
* Searches for the first ancestor directory of a module file that contains a
* package.json.
* Probes by attempting to read the file, not stat.
* To avoid duplicate work later, returns the text of the package.json for
* inevitable later use.
*
* @param {ReadFn} read
* @param {string} moduleLocation
* @returns {Promise<{
* packageLocation: string,
* packageDescriptorLocation: string,
* packageDescriptorText: string,
* moduleSpecifier: string,
* }>}
*/
export const search = async (read, moduleLocation) => {
const {
data,
directory,
location,
packageDescriptorLocation,
} = await searchDescriptor(moduleLocation, loc =>
readDescriptorDefault(read, loc),
);

if (!data) {
throw new Error(
`Cannot find package.json along path to module ${q(moduleLocation)}`,
);
}

return {
packageLocation: directory,
packageDescriptorText: data,
packageDescriptorLocation,
moduleSpecifier: relativize(relative(directory, location)),
};
};

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

140 changes: 140 additions & 0 deletions packages/compartment-mapper/test/snapshots/test-infer-exports.js.md
@@ -0,0 +1,140 @@
# Snapshot report for `test/test-infer-exports.js`

The actual snapshot is saved in `test-infer-exports.js.snap`.

Generated by [AVA](https://avajs.dev).

## infer-exports for @floating-ui/dom

> Snapshot 1
{
exports: {
'@floating-ui/dom': './dist/floating-ui.dom.esm.js',
'@floating-ui/dom/package.json': './package.json',
},
types: {
'./dist/floating-ui.dom.esm.js': 'mjs',
},
}

## infer-exports for @noble/hashes

> Snapshot 1
{
exports: {
'@noble/hashes/_sha2': './_sha2.js',
'@noble/hashes/blake2b': './blake2b.js',
'@noble/hashes/blake2b.d.ts': './blake2b.d.ts',
'@noble/hashes/blake2s': './blake2s.js',
'@noble/hashes/blake2s.d.ts': './blake2s.d.ts',
'@noble/hashes/blake3': './blake3.js',
'@noble/hashes/blake3.d.ts': './blake3.d.ts',
'@noble/hashes/crypto': './crypto.js',
'@noble/hashes/eskdf': './eskdf.js',
'@noble/hashes/eskdf.d.ts': './eskdf.d.ts',
'@noble/hashes/hkdf': './hkdf.js',
'@noble/hashes/hkdf.d.ts': './hkdf.d.ts',
'@noble/hashes/hmac': './hmac.js',
'@noble/hashes/hmac.d.ts': './hmac.d.ts',
'@noble/hashes/index': './index.js',
'@noble/hashes/pbkdf2': './pbkdf2.js',
'@noble/hashes/pbkdf2.d.ts': './pbkdf2.d.ts',
'@noble/hashes/ripemd160': './ripemd160.js',
'@noble/hashes/ripemd160.d.ts': './ripemd160.d.ts',
'@noble/hashes/scrypt': './scrypt.js',
'@noble/hashes/scrypt.d.ts': './scrypt.d.ts',
'@noble/hashes/sha256': './sha256.js',
'@noble/hashes/sha256.d.ts': './sha256.d.ts',
'@noble/hashes/sha3': './sha3.js',
'@noble/hashes/sha3-addons': './sha3-addons.js',
'@noble/hashes/sha3-addons.d.ts': './sha3-addons.d.ts',
'@noble/hashes/sha3.d.ts': './sha3.d.ts',
'@noble/hashes/sha512': './sha512.js',
'@noble/hashes/sha512.d.ts': './sha512.d.ts',
'@noble/hashes/utils': './utils.js',
'@noble/hashes/utils.d.ts': './utils.d.ts',
},
types: {},
}

## infer-exports for babel-plugin-polyfill-es-shims

> Snapshot 1
{
exports: {
'babel-plugin-polyfill-es-shims': './lib/index.js',
'babel-plugin-polyfill-es-shims/package.json': './package.json',
},
types: {},
}

## infer-exports for color2k

> Snapshot 1
{
exports: {
color2k: './dist/index.exports.require.cjs.js',
'color2k/package.json': './package.json',
},
types: {
'./dist/index.module.es.js': 'mjs',
},
}

## infer-exports for socket.io-client

> Snapshot 1
{
exports: {
'socket.io-client': './build/esm/index.js',
'socket.io-client/dist/socket.io.js': './dist/socket.io.js',
'socket.io-client/dist/socket.io.js.map': './dist/socket.io.js.map',
'socket.io-client/package.json': './package.json',
},
types: {
'./build/esm/index.js': 'mjs',
},
}

## infer-exports for underscore

> Snapshot 1
{
exports: {
underscore: './underscore-umd.js',
'underscore/amd/*': './amd/*',
'underscore/cjs/*': './cjs/*',
'underscore/modules/*': './modules/*',
'underscore/package.json': './package.json',
'underscore/underscore*': './underscore*',
},
types: {
'./modules/index-all.js': 'mjs',
},
}

## infer-exports for vue

> Snapshot 1
{
exports: {
vue: './dist/vue.runtime.esm-bundler.js',
'vue/compiler-sfc': './compiler-sfc/index.mjs',
'vue/dist/*': './dist/*',
'vue/macros': './macros.d.ts',
'vue/macros-global': './macros-global.d.ts',
'vue/package.json': './package.json',
'vue/ref-macros': './ref-macros.d.ts',
'vue/server-renderer': './server-renderer/index.mjs',
},
types: {
'./dist/vue.runtime.esm-bundler.js': 'mjs',
},
}
Binary file not shown.

0 comments on commit 4b1c6f4

Please sign in to comment.