Skip to content

Commit

Permalink
Error when dependency on external helpers is incompatible (#8224)
Browse files Browse the repository at this point in the history
  • Loading branch information
devongovett committed Jun 17, 2022
1 parent 8dbc4b2 commit 54e1769
Show file tree
Hide file tree
Showing 13 changed files with 185 additions and 3 deletions.
3 changes: 3 additions & 0 deletions packages/core/core/src/Dependency.js
Expand Up @@ -6,6 +6,7 @@ import type {
SourceLocation,
Symbol,
BundleBehavior as IBundleBehavior,
SemverRange,
} from '@parcel/types';
import type {Dependency, Environment, Target} from './types';
import {hashString} from '@parcel/hash';
Expand All @@ -29,6 +30,7 @@ type DependencyOpts = {|
env: Environment,
meta?: Meta,
resolveFrom?: FilePath,
range?: SemverRange,
target?: Target,
symbols?: Map<
Symbol,
Expand Down Expand Up @@ -69,6 +71,7 @@ export function createDependency(
isEntry: opts.isEntry ?? false,
isOptional: opts.isOptional ?? false,
meta: opts.meta || {},
range: opts.range,
symbols:
opts.symbols &&
new Map(
Expand Down
4 changes: 4 additions & 0 deletions packages/core/core/src/public/Dependency.js
Expand Up @@ -135,6 +135,10 @@ export default class Dependency implements IDependency {
);
}

get range(): ?string {
return this.#dep.range;
}

get pipeline(): ?string {
return this.#dep.pipeline;
}
Expand Down
1 change: 1 addition & 0 deletions packages/core/core/src/types.js
Expand Up @@ -131,6 +131,7 @@ export type Dependency = {|
sourcePath: ?ProjectPath,
sourceAssetType?: ?string,
resolveFrom: ?ProjectPath,
range: ?SemverRange,
symbols: ?Map<
Symbol,
{|
Expand Down
Expand Up @@ -3,6 +3,6 @@
"module": "dist/module.js",
"browserslist": "IE >= 11",
"dependencies": {
"@swc/helpers": "*"
"@swc/helpers": "^0.4.2"
}
}
90 changes: 90 additions & 0 deletions packages/core/integration-tests/test/javascript.js
Expand Up @@ -5639,6 +5639,96 @@ describe('javascript', function () {
);
});

it('should error on mismatched helpers version for libraries', async function () {
let fixture = path.join(
__dirname,
'integration/undeclared-external/helpers.js',
);
let pkg = path.join(
__dirname,
'integration/undeclared-external/package.json',
);
let pkgContents = JSON.stringify(
{
...JSON.parse(await overlayFS.readFile(pkg, 'utf8')),
dependencies: {
'@swc/helpers': '^0.3.0',
},
},
false,
2,
);
await overlayFS.mkdirp(path.dirname(pkg));
await overlayFS.writeFile(pkg, pkgContents);
await assert.rejects(
() =>
bundle(fixture, {
mode: 'production',
inputFS: overlayFS,
defaultTargetOptions: {
shouldOptimize: false,
},
}),
{
name: 'BuildError',
diagnostics: [
{
message: md`Failed to resolve '${'@swc/helpers/lib/_class_call_check.js'}' from '${normalizePath(
require.resolve('@parcel/transformer-js/src/JSTransformer.js'),
)}'`,
origin: '@parcel/core',
codeFrames: [
{
code: await inputFS.readFile(fixture, 'utf8'),
filePath: fixture,
codeHighlights: [
{
start: {
line: 1,
column: 1,
},
end: {
line: 1,
column: 1,
},
},
],
},
],
},
{
message:
'External dependency "@swc/helpers" does not satisfy required semver range "^0.4.2".',
origin: '@parcel/resolver-default',
codeFrames: [
{
code: pkgContents,
filePath: pkg,
language: 'json',
codeHighlights: [
{
message: 'Found this conflicting requirement.',
start: {
line: 6,
column: 21,
},
end: {
line: 6,
column: 28,
},
},
],
},
],
hints: [
'Update the dependency on "@swc/helpers" to satisfy "^0.4.2".',
],
},
],
},
);
});

describe('multiple import types', function () {
it('supports both static and dynamic imports to the same specifier in the same file', async function () {
let b = await bundle(
Expand Down
4 changes: 4 additions & 0 deletions packages/core/types/index.js
Expand Up @@ -525,6 +525,8 @@ export type DependencyOptions = {|
* By default, this is the path of the source file where the dependency was specified.
*/
+resolveFrom?: FilePath,
/** The semver version range expected for the dependency. */
+range?: SemverRange,
/** The symbols within the resolved module that the source file depends on. */
+symbols?: $ReadOnlyMap<
Symbol,
Expand Down Expand Up @@ -601,6 +603,8 @@ export interface Dependency {
* By default, this is the path of the source file where the dependency was specified.
*/
+resolveFrom: ?FilePath;
/** The semver version range expected for the dependency. */
+range: ?SemverRange;
/** The pipeline defined in .parcelrc that the dependency should be processed with. */
+pipeline: ?string;

Expand Down
1 change: 1 addition & 0 deletions packages/resolvers/default/src/DefaultResolver.js
Expand Up @@ -34,6 +34,7 @@ export default (new Resolver({
return resolver.resolve({
filename: specifier,
specifierType: dependency.specifierType,
range: dependency.range,
parent: dependency.resolveFrom,
env: dependency.env,
sourcePath: dependency.sourcePath,
Expand Down
1 change: 1 addition & 0 deletions packages/resolvers/glob/src/GlobResolver.js
Expand Up @@ -99,6 +99,7 @@ export default (new Resolver({
invalidateOnFileChange,
specifierType: dependency.specifierType,
loc: dependency.loc,
range: dependency.range,
};

let result;
Expand Down
13 changes: 13 additions & 0 deletions packages/transformers/js/src/JSTransformer.js
Expand Up @@ -12,6 +12,7 @@ import nullthrows from 'nullthrows';
import ThrowableDiagnostic, {encodeJSONKeyComponent} from '@parcel/diagnostic';
import {validateSchema, remapSourceLocation, isGlobMatch} from '@parcel/utils';
import WorkerFarm from '@parcel/workers';
import pkg from '../package.json';

const JSX_EXTENSIONS = {
jsx: true,
Expand Down Expand Up @@ -689,6 +690,17 @@ export default (new Transformer({
};
}

// Add required version range for helpers.
let range;
if (isHelper) {
let idx = dep.specifier.indexOf('/');
if (dep.specifier[0] === '@') {
idx = dep.specifier.indexOf('/', idx + 1);
}
let module = idx >= 0 ? dep.specifier.slice(0, idx) : dep.specifier;
range = pkg.dependencies[module];
}

asset.addDependency({
specifier: dep.specifier,
specifierType: dep.kind === 'Require' ? 'commonjs' : 'esm',
Expand All @@ -697,6 +709,7 @@ export default (new Transformer({
isOptional: dep.is_optional,
meta,
resolveFrom: isHelper ? __filename : undefined,
range,
env,
});
}
Expand Down
3 changes: 2 additions & 1 deletion packages/utils/node-resolver-core/package.json
Expand Up @@ -21,7 +21,8 @@
"dependencies": {
"@parcel/diagnostic": "2.6.0",
"@parcel/utils": "2.6.0",
"nullthrows": "^1.1.1"
"nullthrows": "^1.1.1",
"semver": "^5.7.1"
},
"devDependencies": {
"assert": "^2.0.0",
Expand Down
41 changes: 41 additions & 0 deletions packages/utils/node-resolver-core/src/NodeResolver.js
Expand Up @@ -8,6 +8,7 @@ import type {
SpecifierType,
PluginLogger,
SourceLocation,
SemverRange,
} from '@parcel/types';
import type {FileSystem} from '@parcel/fs';
import type {PackageManager} from '@parcel/package-manager';
Expand All @@ -27,11 +28,13 @@ import {
import ThrowableDiagnostic, {
generateJSONCodeHighlights,
md,
encodeJSONKeyComponent,
} from '@parcel/diagnostic';
import builtins, {empty} from './builtins';
import nullthrows from 'nullthrows';
import _Module from 'module';
import {fileURLToPath} from 'url';
import semver from 'semver';

const EMPTY_SHIM = require.resolve('./_empty');

Expand Down Expand Up @@ -68,6 +71,7 @@ type ResolverContext = {|
invalidateOnFileCreate: Array<FileCreateInvalidation>,
invalidateOnFileChange: Set<FilePath>,
specifierType: SpecifierType,
range: ?SemverRange,
loc: ?SourceLocation,
|};

Expand Down Expand Up @@ -111,13 +115,15 @@ export default class NodeResolver {
filename,
parent,
specifierType,
range,
env,
sourcePath,
loc,
}: {|
filename: FilePath,
parent: ?FilePath,
specifierType: SpecifierType,
range?: ?SemverRange,
env: Environment,
sourcePath?: ?FilePath,
loc?: ?SourceLocation,
Expand All @@ -126,6 +132,7 @@ export default class NodeResolver {
invalidateOnFileCreate: [],
invalidateOnFileChange: new Set(),
specifierType,
range,
loc,
};

Expand Down Expand Up @@ -485,6 +492,40 @@ export default class NodeResolver {
},
});
}

if (ctx.range) {
let range = ctx.range;
let depRange =
pkg.dependencies?.[moduleName] || pkg.peerDependencies?.[moduleName];
if (depRange && !semver.intersects(depRange, range)) {
let pkgContent = await this.fs.readFile(pkg.pkgfile, 'utf8');
let field = pkg.dependencies?.[moduleName]
? 'dependencies'
: 'peerDependencies';
throw new ThrowableDiagnostic({
diagnostic: {
message: md`External dependency "${moduleName}" does not satisfy required semver range "${range}".`,
codeFrames: [
{
filePath: pkg.pkgfile,
language: 'json',
code: pkgContent,
codeHighlights: generateJSONCodeHighlights(pkgContent, [
{
key: `/${field}/${encodeJSONKeyComponent(moduleName)}`,
type: 'value',
message: 'Found this conflicting requirement.',
},
]),
},
],
hints: [
`Update the dependency on "${moduleName}" to satisfy "${range}".`,
],
},
});
}
}
}

async resolveFilename(
Expand Down
Expand Up @@ -11,7 +11,7 @@
"glob/*/*": "./nested/$2"
},
"dependencies": {
"foo": "*"
"foo": "^0.3.4"
},
"peerDependencies": {
"bar": "*"
Expand Down
23 changes: 23 additions & 0 deletions packages/utils/node-resolver-core/test/resolver.js
Expand Up @@ -2679,6 +2679,29 @@ describe('resolver', function () {

assert.deepEqual(result, {isExcluded: true});
});

it('should error when a library has an incorrect external dependency version', async function () {
let result = await resolver.resolve({
env: new Environment(
createEnvironment({
context: 'browser',
isLibrary: true,
includeNodeModules: false,
}),
DEFAULT_OPTIONS,
),
filename: 'foo',
specifierType: 'esm',
range: '^0.4.0',
parent: path.join(rootDir, 'foo.js'),
sourcePath: path.join(rootDir, 'foo.js'),
});

assert.equal(
result?.diagnostics?.[0].message,
'External dependency "foo" does not satisfy required semver range "^0.4.0".',
);
});
});

describe('urls', function () {
Expand Down

0 comments on commit 54e1769

Please sign in to comment.