-
Notifications
You must be signed in to change notification settings - Fork 137
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
Refuse to accept v1 addons as invalid peerDeps #1542
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import Package from './package'; | ||
|
||
// For each package in the graph, discover all the paths to reach that package. | ||
// That is, we're identifying all its consumers. | ||
export function crawlDeps(startingPackage: Package): Map<Package, Package[][]> { | ||
let queue: { pkg: Package; path: Package[] }[] = [{ pkg: startingPackage, path: [] }]; | ||
let seen = new Set<Package>(); | ||
let results = new Map<Package, Package[][]>(); | ||
for (;;) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unrelated to the goal of the Pr, but moreso a curiosity, any reason to favor one method of implicitly-bounded looping over another? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They're equivalent. I got into the habit of using this one because I've encountered lint rules that forbid |
||
let entry = queue.shift(); | ||
if (!entry) { | ||
break; | ||
} | ||
let { pkg, path } = entry; | ||
|
||
let paths = results.get(pkg); | ||
if (paths) { | ||
paths.push(path); | ||
} else { | ||
results.set(pkg, [path]); | ||
} | ||
|
||
if (!seen.has(pkg)) { | ||
seen.add(pkg); | ||
for (let dep of pkg.dependencies) { | ||
if (pkg.categorizeDependency(dep.name) !== 'peerDependencies') { | ||
queue.push({ pkg: dep, path: [...path, pkg] }); | ||
} | ||
} | ||
} | ||
} | ||
return results; | ||
} | ||
|
||
interface PeerDepViolation { | ||
// this package... | ||
pkg: Package; | ||
// sees this peer dep. | ||
dep: Package; | ||
// pkg was reached by this path of ancestors | ||
ancestors: Package[]; | ||
// this particular ancestor... | ||
ancestor: Package; | ||
// provides a conflicting value for the peerDep | ||
ancestorsDep: Package; | ||
} | ||
|
||
export function validatePeerDependencies(appPackage: Package): PeerDepViolation[] { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this pretty fast? academically, it's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not really N^4 (where N is the number of packages), because those are different N's at the different levels. The outermost level is O(N) where N=number of packages. But the next level is O(P) where P = average number of peer deps a package has. Which is probably closer to zero than one. That immediately chops off a vast swath of the space. Then if you do have peer deps, the next level is the number of times your package is consumed, and the final level is the number of level we have to look upward to find the provider of the peer dep. It's quite fast, and it's all traversing our in-memory package cache. |
||
let violations = []; | ||
for (let [pkg, consumptions] of crawlDeps(appPackage)) { | ||
for (let dep of pkg.dependencies) { | ||
if (pkg.categorizeDependency(dep.name) === 'peerDependencies') { | ||
for (let ancestors of consumptions) { | ||
for (let ancestor of ancestors.slice().reverse()) { | ||
if (ancestor.hasDependency(dep.name)) { | ||
let ancestorsDep = ancestor.dependencies.find(d => d.name === dep.name)!; | ||
if (ancestorsDep !== dep && dep.isEmberPackage()) { | ||
violations.push({ pkg, dep, ancestors, ancestor, ancestorsDep }); | ||
} | ||
continue; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
return violations; | ||
} | ||
|
||
export function summarizePeerDepViolations(violations: PeerDepViolation[]): string { | ||
let message = []; | ||
for (let { pkg, dep, ancestors, ancestor, ancestorsDep } of violations) { | ||
for (let [index, a] of ancestors.entries()) { | ||
message.push(packageIdSummary(a)); | ||
if (index + 1 < ancestors.length) { | ||
message.push(displayConnection(a, ancestors[index + 1])); | ||
} else { | ||
message.push(displayConnection(a, pkg)); | ||
} | ||
} | ||
message.push(packageIdSummary(pkg)); | ||
message.push('\n'); | ||
message.push(` sees peerDep ${packageIdSummary(dep)}\n at ${dep.root}\n`); | ||
message.push( | ||
` but ${packageIdSummary(ancestor)} is using ${packageIdSummary(ancestorsDep)}\n at ${ | ||
ancestorsDep.root | ||
}\n\n` | ||
); | ||
} | ||
return message.join(''); | ||
} | ||
|
||
function displayConnection(left: Package, right: Package) { | ||
if (left.packageJSON.dependencies?.[right.name]) { | ||
return ' -> '; | ||
} | ||
if (left.packageJSON.peerDependencies?.[right.name]) { | ||
return ' (peer)-> '; | ||
} | ||
if (left.packageJSON.devDependencies?.[right.name]) { | ||
return ' (dev)-> '; | ||
} | ||
return ' (?)-> '; | ||
} | ||
|
||
function packageIdSummary(pkg: Package): string { | ||
return `${pkg.name}@${pkg.version}`; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉 I love it