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

fix(compiler-cli): use numeric comparison for TypeScript version #22705

Closed
wants to merge 2 commits into from

Conversation

benbraou
Copy link
Contributor

Fixes #22593

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

[x] Bugfix
[ ] Feature
[ ] Code style update (formatting, local variables)
[ ] Refactoring (no functional changes, no api changes)
[ ] Build related changes
[ ] CI related changes
[ ] Documentation content changes
[ ] angular.io application / infrastructure changes
[ ] Other... Please describe:

What is the current behavior?

Typescript version is compared using string compare

Issue Number: N/A
#22593

What is the new behavior?

Typescript version is compared using numeric compare

Does this PR introduce a breaking change?

[ ] Yes
[x] No

Other information

if (ts.version < '2.4.2' || (ts.version >= '2.7.0' && !options.disableTypeScriptVersionCheck)) {

if (compareVersions(ts.version, '2.4.2') < 0 ||
(compareVersions(ts.version, '2.7.0') >= 0 && !options.disableTypeScriptVersionCheck)) {
throw new Error(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

No tests were added in this PR regarding the error thrown because they are being added in #22701 .

@benbraou benbraou force-pushed the issue22593 branch 2 times, most recently from 4e23a0b to c21f67f Compare March 11, 2018 16:12
@benbraou
Copy link
Contributor Author

Bug fix separated in 2 commits:

  1. rename symbol_query_spec.ts to typescript_symbols_spec.ts
  2. usage of numeric comparison instead of string comparison

By the way, diff of first commit 7d45afa does show that there has been a rename (however, the full PR diff shows that a file has been deleted and a new one added)

@kara kara added area: core Issues related to the framework runtime action: review The PR is still awaiting reviews from at least one requested reviewer labels Mar 12, 2018
@ngbot
Copy link

ngbot bot commented Mar 12, 2018

Hi @benbraou! This PR has merge conflicts due to recent upstream merges.
Please help to unblock it by resolving these conflicts. Thanks!

@benbraou
Copy link
Contributor Author

I am going to do the rebase to handle the recent merges
Also, to ease testing, I will isolate typescript version check a separate function

export function checkTypeScriptVersion(
  version: string,
  disableTypeScriptVersionCheck: boolean | undefined
) {
  if ((version < '2.7.2' || version >= '2.8.0') && !disableTypeScriptVersionCheck) {
    throw new Error(
        `The Angular Compiler requires TypeScript >=2.7.2 and <2.8.0 but ${version} was found instead.`);
  }
}

@benbraou benbraou changed the title fix(compiler-cli): use numeric comparison for TypeScript version [WIP] fix(compiler-cli): use numeric comparison for TypeScript version Mar 12, 2018
@@ -858,3 +858,7 @@ function isTypescriptVersion(low: string, high?: string): boolean {
return compareNumbers(toNumbers(low), tsNumbers) <= 0 &&
compareNumbers(toNumbers(high), tsNumbers) >= 0;
}

export function compareVersions(version1: string, version2: string): -1|0|1 {
return compareNumbers(toNumbers(version1), toNumbers(version2));
Copy link
Contributor

Choose a reason for hiding this comment

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

isn't compareVersions('2', '2.1') === 0 with this code ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed. I did not check the implementation of compareNumbers. Fixing + adding new tests

@vicb
Copy link
Contributor

vicb commented Mar 12, 2018

The first commit should be a refactor (does not "fix" anything). Thanks.

@benbraou benbraou force-pushed the issue22593 branch 2 times, most recently from b9412eb to 51bb8b6 Compare March 13, 2018 03:43
@benbraou benbraou changed the title [WIP] fix(compiler-cli): use numeric comparison for TypeScript version fix(compiler-cli): use numeric comparison for TypeScript version Mar 13, 2018
@benbraou
Copy link
Contributor Author

@vicb Thanks for the review. PR updated

  1. Utility functions that check TypeScript version are moved to a new file typescript_version.ts
  2. compareNumbers method is updated
  3. unit tests added for typescript_version.ts
  4. unit test added for checkTypeScriptVersion in program_spec.ts
  5. Only one commit is now needed. symbol_query_spec.ts is no longer renamed

@@ -751,6 +748,14 @@ class AngularCompilerProgram implements Program {
}
}

export function checkTypeScriptVersion(
version: string, disableTypeScriptVersionCheck: boolean | undefined) {
if ((version < '2.7.2' || version >= '2.8.0') && !disableTypeScriptVersionCheck) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We should probably use compareVersions here ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also what about moving the version strings to const MIN_TS_VERSION and MAX_TS_VERSION ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed. I will implemented the change for both comments


describe('toNumbers', () => {

it('should handle undefined value', () => { expect(toNumbers(undefined)).toEqual([]); });
Copy link
Contributor

Choose a reason for hiding this comment

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

should it ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  • I was viewing this test as a "behavior documentation" of a method implemented prior to my commit.
  • No, it should not handle undefined. I am changing the signature and implementation of toNumbers
  • Great catch btw. This allowed me to discover a regression that would have been introduced. Please check my comment in describe('isTypescriptVersion', () => {

it('should handle undefined value', () => { expect(toNumbers(undefined)).toEqual([]); });

it('should handle strings', () => {
expect(toNumbers('')).toEqual([]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why would this ever be valid ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can't think of a reason. I am removing this assertion but keeping the method implementation. Please let me know in case this is not enough

Copy link
Contributor

@vicb vicb left a comment

Choose a reason for hiding this comment

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

Mostly LGTM.

  • One change required,
  • Why do we need to support toNumbers returning an empty array ?


describe('toNumbers', () => {

it('should handle undefined value', () => { expect(toNumbers(undefined)).toEqual([]); });
Copy link
Contributor Author

Choose a reason for hiding this comment

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

  • I was viewing this test as a "behavior documentation" of a method implemented prior to my commit.
  • No, it should not handle undefined. I am changing the signature and implementation of toNumbers
  • Great catch btw. This allowed me to discover a regression that would have been introduced. Please check my comment in describe('isTypescriptVersion', () => {

it('should handle undefined value', () => { expect(toNumbers(undefined)).toEqual([]); });

it('should handle strings', () => {
expect(toNumbers('')).toEqual([]);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can't think of a reason. I am removing this assertion but keeping the method implementation. Please let me know in case this is not enough

describe('isTypescriptVersion', () => {
it('should correctly check if a typescript version is within a given range', () => {
expect(isTypeScriptVersion('2.7.0', '2.40')).toEqual(false);
expect(isTypeScriptVersion('2.40', '2.7.0')).toEqual(false);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This assertion is incorrect.
2.40 > 2.7.0 => isTypeScriptVersion('2.40', '2.7.0') should be true and not false
Why is this happening ?

export function isTypeScriptVersion(version: string, low: string, high?: string): boolean {
const tsNumbers = toNumbers(version);
 return compareNumbers(toNumbers(low), tsNumbers) <= 0 &&
  compareNumbers(toNumbers(high), tsNumbers) >= 0;
}
  • isTypeScriptVersion('2.40', '2.7.0') ( => high = undefined) uses internally the comparison compareNumbers(toNumbers(undefined),[2, 40]) >= 0
  • toNumbers(undefined) is returning [] (This is counter intuitive as @vicb noticed)
  • So we end up comparing [] and [2,40]
    • Prior to commit, [] = [2,40] because the comparison stops at the min length of both array (here 0) and defaulting to 0
    • With my commit, [] < [2,40] (and this makes a lot of sense for me). So, compareNumbers(toNumbers(undefined),[2, 40] ) >= 0 returns false . And so,isTypeScriptVersion('2.40', '2.7.0') incorrectly returns false

Fix ?

  • as @vicb suggested, toNumbers(undefined) should not even compile
  • Improve the implementation of isTypeScriptVersion: check high parameter is defined before performing the comparison
  • isTypeScriptVersion is only used in typescript_symbols.ts to create SymbolTable. Since this regression did not break any test, I am adding a unit test for toSymbolTable method

@benbraou
Copy link
Contributor Author

@vicb thanks for the review. All requested changes/questions are addressed and explained in my comments.
A regression with isTypeScriptVersion was fixed (I have explained my finding in my review of typescript_version_spec.ts)
I have added two new commits to better highlight the changes.

I see that e2e tests are failing because of this error
Error: XHR error loading http://localhost:8000/all/benchmarks/vendor/incremental-dom-cjs.js
I don't see how it is linked to this PR but I am having a look (maybe trigger a new build ?)

@benbraou benbraou force-pushed the issue22593 branch 2 times, most recently from ea67dc6 to 387826e Compare March 24, 2018 15:21
@benbraou
Copy link
Contributor Author

After forcing a new build, I only see CI_MODE=saucelabs_required failing due to timeout issues. I am seeing this same failure in other ongoing PRs


const MIN_TS_VERSION_SUPPORTING_MAP = '2.2';
Copy link
Contributor

Choose a reason for hiding this comment

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

move that right before where it is used and/or add a one line comment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

will be moved and commented

}
return result as ts.SymbolTable;
});
export const toSymbolTable = (tsVersion: string) => (symbols: ts.Symbol[]) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Rename toSymbolTableFactory ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok

@vicb vicb removed the action: review The PR is still awaiting reviews from at least one requested reviewer label Mar 29, 2018
@vicb
Copy link
Contributor

vicb commented Mar 29, 2018

@vicb vicb self-assigned this Mar 29, 2018
@vicb
Copy link
Contributor

vicb commented Mar 29, 2018

blocked on failing tests internally

[...]/packages/compiler-cli/src/diagnostics/typescript_symbols.ts(410,12): error TS2352: Type 'Map<string, Symbol>' cannot be converted to type 'UnderscoreEscapedMap<Symbol>'.
  Types of property 'forEach' are incompatible.
    Type '(callbackfn: (value: Symbol, key: string, map: Map<string, Symbol>) => void, thisArg?: any) => void' is not comparable to type '(action: (value: Symbol, key: __String) => void) => void'.
      Types of parameters 'callbackfn' and 'action' are incompatible.
        Types of parameters 'key' and 'key' are incompatible.
          Type 'string' is not comparable to type '__String'.

@benbraou
Copy link
Contributor Author

@vicb in typescript_symbols.ts, I am going to add any type assertion

So what we currently have:

  // ∀ Typescript version >= 2.2, `SymbolTable` is implemented as an ES6 `Map`
    const result = new Map<string, ts.Symbol>();
    for (const symbol of symbols) {
      result.set(symbol.name, symbol);
    }
    return <ts.SymbolTable>result;

will become

export const toSymbolTableFactory = (tsVersion: string) => (symbols: ts.Symbol[]) => {
  if (isVersionBetween(tsVersion, MIN_TS_VERSION_SUPPORTING_MAP)) {
    // ∀ Typescript version >= 2.2, `SymbolTable` is implemented as an ES6 `Map`
    const result = new Map<string, ts.Symbol>();
    for (const symbol of symbols) {
      result.set(symbol.name, symbol);
    }
    // First, tell the compiler that `result` is of type `any`. Then, use a second type assertion
    // to `ts.SymbolTable`.
    // Otherwise, `Map<string, ts.Symbol>` and `ts.SymbolTable` will be considered as incompatible
    // types by the compiler
    return <ts.SymbolTable>(<any>result);
  }

I am going to amend 6f02545

@benbraou
Copy link
Contributor Author

rebased on top of master again to force a new build.
Previous one has a failing job CI_MODE=saucelabs_required

@benbraou
Copy link
Contributor Author

forced a new build due to failure now in aio_e2e build job

@vicb
Copy link
Contributor

vicb commented Mar 29, 2018

Copy link
Contributor

@alexeagle alexeagle left a comment

Choose a reason for hiding this comment

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

bazel change looks fine to me

@vicb vicb added action: merge The PR is ready for merge by the caretaker and removed state: blocked labels Mar 29, 2018
@vicb
Copy link
Contributor

vicb commented Mar 29, 2018

@benbraou This is ready to be merged. Thank you very much for your work on that.

@vicb vicb added the merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note label Mar 29, 2018
@vicb
Copy link
Contributor

vicb commented Mar 29, 2018

merge-assistance: This PR is green - ci/angular status is off

@ocombe ocombe added target: major This PR is targeted for the next major release and removed merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note target: major This PR is targeted for the next major release labels Mar 30, 2018
@alxhub alxhub closed this in 193737a Mar 30, 2018
@benbraou benbraou deleted the issue22593 branch March 30, 2018 17:20
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 13, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker area: core Issues related to the framework runtime cla: yes target: major This PR is targeted for the next major release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Compiler TypeScript version checking too simplistic
7 participants