Skip to content

add an exception to the for-each-of lint rule for interval iteration#709

Merged
michaelficarra merged 2 commits into
mainfrom
for-each-interval
Jul 9, 2026
Merged

add an exception to the for-each-of lint rule for interval iteration#709
michaelficarra merged 2 commits into
mainfrom
for-each-interval

Conversation

@michaelficarra

@michaelficarra michaelficarra commented Jul 9, 2026

Copy link
Copy Markdown
Member

Fixes #708

Comment thread src/lint/rules/for-each-of.ts Outdated
/^ in\b/.test(next.contents) &&
// Iterating over an interval is a valid construction where "in" is correct, in either the
// "inclusive interval from _a_ to _b_" shorthand or the full "interval from _a_ ... to _b_ ..." form.
!/^ in the (?:inclusive )?interval from\b/.test(next.contents)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
!/^ in the (?:inclusive )?interval from\b/.test(next.contents)
!/^ in the (?:inclusive )?interval\b/.test(next.contents)

No need to mandate the "from"; e.g. one might say "in the interval defined by..." or whatever.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of our allowed interval forms use from: https://tc39.es/ecma262/#interval

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I second @bakkot's suggestion to stop after "interval". And if we consider it in scope, also adopting the early-exit pattern from above this loop:

  // Find the loop variable (underscore), then check the text after it
  for (let i = 1; i < stepSeq.items.length - 1; i++) {
    const item = stepSeq.items[i];
    if (item.name !== 'underscore') {
      continue;
    }

    const next = stepSeq.items[i + 1];
    const over = next.name === 'text'
      ? next.contents.match(/^ in (the (?:inclusive)? interval )?/)
      : undefined;
    if (over && !over[1]) {
      report({
        ruleId,
        ...offsetToLineAndColumn(algorithmSource, next.location.start.offset + 1),
        message: 'expected "of" instead of "in" in "for each"',
      });
    }
    break;
  }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of our allowed interval forms use from: https://tc39.es/ecma262/#interval

This rule is not intended to enforce a specific style for declaring intervals. You could have such a rule if you really wanted, but I would still want this rule not to enforce it; writing "in the interval defined by..." shouldn't lead to an error complaining about using in instead of of even if there's some other error that complains about some other thing.

@gibson042 gibson042 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM even without accepting the suggestion(s).

Comment thread src/lint/rules/for-each-of.ts Outdated
/^ in\b/.test(next.contents) &&
// Iterating over an interval is a valid construction where "in" is correct, in either the
// "inclusive interval from _a_ to _b_" shorthand or the full "interval from _a_ ... to _b_ ..." form.
!/^ in the (?:inclusive )?interval from\b/.test(next.contents)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I second @bakkot's suggestion to stop after "interval". And if we consider it in scope, also adopting the early-exit pattern from above this loop:

  // Find the loop variable (underscore), then check the text after it
  for (let i = 1; i < stepSeq.items.length - 1; i++) {
    const item = stepSeq.items[i];
    if (item.name !== 'underscore') {
      continue;
    }

    const next = stepSeq.items[i + 1];
    const over = next.name === 'text'
      ? next.contents.match(/^ in (the (?:inclusive)? interval )?/)
      : undefined;
    if (over && !over[1]) {
      report({
        ruleId,
        ...offsetToLineAndColumn(algorithmSource, next.location.start.offset + 1),
        message: 'expected "of" instead of "in" in "for each"',
      });
    }
    break;
  }

@michaelficarra
michaelficarra merged commit 17c8ab3 into main Jul 9, 2026
2 checks passed
@michaelficarra
michaelficarra deleted the for-each-interval branch July 9, 2026 23:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Linting should not complain about "For each $x in the inclusive interval from $a to $b"

3 participants