Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error when dependency on external helpers is incompatible #8224

Merged
merged 3 commits into from Jun 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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