diff --git a/src/analysis/dependency-parser.ts b/src/analysis/dependency-parser.ts index 3be32679a..40e5541ae 100644 --- a/src/analysis/dependency-parser.ts +++ b/src/analysis/dependency-parser.ts @@ -70,55 +70,39 @@ class DependencyParser { } parse(): Result { - if (this.#peek() === PERIOD) { - return this.#parseBackwardsCompatible(); - } + const startsWithPeriod = this.#peek() === PERIOD; - const first = this.#parseParts(); + const first = this.#parseParts(startsWithPeriod); if (this.#done()) { + if (startsWithPeriod) { + return { + ok: true, + value: {package: first, script: []}, + }; + } return { ok: true, value: {package: [], script: first}, }; } - this.#skip(); - const second = this.#parseParts(); - if (!this.#done()) { - return { - ok: true, - value: {package: [], script: []}, - }; - } - return { - ok: true, - value: {package: first, script: second}, - }; - } - #parseBackwardsCompatible(): Result { - const pkg = this.#parseParts(true); - if (this.#done()) { - return { - ok: true, - value: {package: pkg, script: []}, - }; - } // Assume it's either a "#" or a ":". this.#skip(); - const script = this.#parseParts(); + const second = this.#parseParts(); if (!this.#done()) { return { + // TODO(aomarks) Error ok: true, value: {package: [], script: []}, }; } return { ok: true, - value: {package: pkg, script}, + value: {package: first, script: second}, }; } - #parseParts(isPathPosition = false): Part[] { + #parseParts(isInPathPosition = false): Part[] { let buffer = ''; const parts: Part[] = []; while (this.#pos < this.#len) { @@ -137,22 +121,33 @@ class DependencyParser { } else if (this.#peek() === HASH) { break; } else if ( - isPathPosition && + isInPathPosition && this.#peek() === COLON && - !this.#lookAhead('#', 1) + !this.#lookAhead(HASH, 1) ) { // This case provides backwards compatibility for the syntax before "#" // was adopted. The rule here covers cases like: // - // ./foo:bar + // "./foo:bar" // // Which we would today recommend writing as: // - // ./foo#bar + // "./foo#bar" // // But which, without this case, would be wrongly interpreted as: // // { package: undefined , script: "./foo:bar" } + // + // Note the reason we switch from ":" to "#" is because of ambiguous + // cases. Consider the two cases: + // + // 1. "build:tsc" + // 2. ":build" + // 3. ":build" + // + // In case (1), the user clearly meant "the script in this package + // called build:tsc", but in case (2) they clearly meant "the package + // called build in all workspaces". break; } else { buffer += this.#consume(); diff --git a/src/test/analysis.test.ts b/src/test/analysis.test.ts index 7c0142665..f10449623 100644 --- a/src/test/analysis.test.ts +++ b/src/test/analysis.test.ts @@ -554,6 +554,21 @@ const cases = [ script: [{kind: 'variable', value: 'this'}], }, ], + [ + 'ab#cd', + { + package: [ + {kind: 'literal', value: 'a'}, + {kind: 'variable', value: 'workspaces'}, + {kind: 'literal', value: 'b'}, + ], + script: [ + {kind: 'literal', value: 'c'}, + {kind: 'variable', value: 'this'}, + {kind: 'literal', value: 'd'}, + ], + }, + ], [ '',