-
Notifications
You must be signed in to change notification settings - Fork 5
NEW @W-19772057@ Enhanced rule selector support #363
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
Merged
+235
−19
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
d188bb3
@W-19772057@ Enhanced rule selector support
jfeingold35 c46b989
@W-19772057@ Feedback from message review
jfeingold35 5fd3cbb
@W-19772057@ Fixing typo in comments
jfeingold35 24a8379
@W-19772057@ Colons resolve before commas
jfeingold35 42d58cc
@W-19772057@ Added inline comments for selectors
jfeingold35 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import { getMessage } from "./messages"; | ||
|
||
export interface Selector { | ||
matchesSelectables(selectables: string[]): boolean; | ||
} | ||
|
||
export function toSelector(selectorString: string): Selector { | ||
// We parse the selector back-to-front, so that the front-most selectors end up at the bottom of the tree we create | ||
// and therefore get resolved first. | ||
if (selectorString === '') { | ||
// ERROR CASE: The selector is empty. Possible if you do something like "()" or "a:()". | ||
throw new Error(getMessage("SelectorCannotBeEmpty")); | ||
} else if (selectorString.endsWith(')')) { | ||
// If the selector ends in close-paren, then we need to find the open-paren that matches it. | ||
const correspondingOpenParen: number = identifyCorrespondingOpenParen(selectorString); | ||
if (correspondingOpenParen === 0) { | ||
// RECURSIVE CASE: The entire selector is wrapped in parens. Pop them off and call recursively. | ||
return toSelector(selectorString.slice(1, -1)) | ||
} else { | ||
// RECURSIVE CASE: The open-paren is somewhere in the middle of the selector and accompanied by an operator. | ||
const left: string = selectorString.slice(0, correspondingOpenParen - 1); | ||
const right: string = selectorString.slice(correspondingOpenParen); | ||
const op: string = selectorString[correspondingOpenParen - 1]; | ||
return toComplexSelector(left, right, op); | ||
} | ||
} else { | ||
const lastComma: number = selectorString.lastIndexOf(','); | ||
const lastColon: number = selectorString.lastIndexOf(':'); | ||
|
||
// BASE CASE: The selector contains no commas or colons. | ||
if (lastComma === -1 && lastColon === -1) { | ||
// Parens only make sense in conjunction with operators, so if we find any, the selector is malformed. | ||
if (selectorString.includes(')') || selectorString.includes('(')) { | ||
throw new Error(getMessage('SelectorLooksIncorrect', selectorString)); | ||
} | ||
return new SimpleSelector(selectorString); | ||
} else if (lastComma !== -1) { | ||
// Commas resolve before colons, so that "x,a:b" and "a:b,x" both resolve equivalently the combination of | ||
// "x" and "a:b". | ||
const left: string = selectorString.slice(0, lastComma); | ||
const right: string = selectorString.slice(lastComma + 1); | ||
return toComplexSelector(left, right, ','); | ||
} else { | ||
const left: string = selectorString.slice(0, lastColon); | ||
const right: string = selectorString.slice(lastColon + 1); | ||
return toComplexSelector(left, right, ':'); | ||
} | ||
} | ||
} | ||
|
||
function identifyCorrespondingOpenParen(selectorString: string): number { | ||
const reversedLetters: string[] = selectorString.split('').reverse(); | ||
let parenBalance: number = 0; | ||
let idx = 0; | ||
for (const letter of reversedLetters) { | ||
if (letter === ')') { | ||
parenBalance += 1; | ||
} else if (letter === '(') { | ||
parenBalance -= 1; | ||
} | ||
if (parenBalance === 0) { | ||
break; | ||
} | ||
idx += 1; | ||
} | ||
|
||
if (parenBalance > 0) { | ||
throw new Error(getMessage("SelectorLooksIncorrect", selectorString)); | ||
} | ||
|
||
return selectorString.length - idx - 1; | ||
} | ||
|
||
function toComplexSelector(left: string, right: string, op: string): Selector { | ||
if (op === ',') { | ||
return new OrSelector(toSelector(left), toSelector(right)); | ||
} else if (op === ':') { | ||
return new AndSelector(toSelector(left), toSelector(right)); | ||
} else { | ||
throw new Error(getMessage("SelectorLooksIncorrect", `${left}${op}${right}`)); | ||
} | ||
} | ||
|
||
class SimpleSelector implements Selector { | ||
private readonly selector: string; | ||
|
||
constructor(selector: string) { | ||
this.selector = selector; | ||
} | ||
|
||
public matchesSelectables(selectables: string[]): boolean { | ||
return selectables.some(s => s === this.selector.toLowerCase()); | ||
} | ||
} | ||
|
||
class AndSelector implements Selector { | ||
private readonly left: Selector; | ||
private readonly right: Selector; | ||
|
||
constructor(left: Selector, right: Selector) { | ||
this.left = left; | ||
this.right = right; | ||
} | ||
|
||
public matchesSelectables(selectables: string[]): boolean { | ||
return this.left.matchesSelectables(selectables) && this.right.matchesSelectables(selectables); | ||
} | ||
} | ||
|
||
class OrSelector implements Selector { | ||
private readonly left: Selector; | ||
private readonly right: Selector; | ||
|
||
constructor(left: Selector, right: Selector) { | ||
this.left = left; | ||
this.right = right; | ||
} | ||
|
||
public matchesSelectables(selectables: string[]): boolean { | ||
return this.left.matchesSelectables(selectables) || this.right.matchesSelectables(selectables); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Is there a logical limit to how many unions and intersections there can be, or does the limit not exist?
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.
The algorithm is just standard recursion, so there's no hardcoded limit.