Angular-cli: Babel does not process loop label correctly

Created on 17 Feb 2020  ·  5Comments  ·  Source: angular/angular-cli

🐞 Bug report

Command


  • [x] build
  • [ ] serve

Is this a regression?


Yes, the previous version in which this bug was not present was: 8.3.25

Description

When running ng build --prod with "target": "es2015", not using Ivy, compilation fails when generating es5 bundles, with a Babel error.
Edit: the error does not occur when building without --prod.

This error had never happened before, and appeared just after v9 update. Seems to be related to a heterodox for-of syntax, maybe in a dependency but which one ?

🔬 Minimal Reproduction

No idea, the app is quite complex…

🔥 Exception or Error


Generating ES5 bundles for differential loading...
An unhandled exception occurred: /home/mathias/src/nghyd/main-es2015.037c4c9354d91b8c3932.js: Property body expected type of array but got null
See "/tmp/ng-bRVUux/angular-errors.log" for further details.

The /tmp/ng-bRVUux/angular-errors.log log file contains:


[error] TypeError: /home/mathias/src/nghyd/main-es2015.037c4c9354d91b8c3932.js: Property body expected type of array but got null
    at validate (/home/mathias/src/nghyd/node_modules/@angular-devkit/build-angular/node_modules/@babel/types/lib/definitions/utils.js:159:13)
    at Object.validate (/home/mathias/src/nghyd/node_modules/@angular-devkit/build-angular/node_modules/@babel/types/lib/definitions/utils.js:196:7)
    at validateField (/home/mathias/src/nghyd/node_modules/@angular-devkit/build-angular/node_modules/@babel/types/lib/validators/validate.js:24:9)
    at Object.validate (/home/mathias/src/nghyd/node_modules/@angular-devkit/build-angular/node_modules/@babel/types/lib/validators/validate.js:17:3)
    at NodePath._replaceWith (/home/mathias/src/nghyd/node_modules/@angular-devkit/build-angular/node_modules/@babel/traverse/lib/path/replacement.js:172:7)
    at NodePath._remove (/home/mathias/src/nghyd/node_modules/@angular-devkit/build-angular/node_modules/@babel/traverse/lib/path/removal.js:53:10)
    at NodePath.remove (/home/mathias/src/nghyd/node_modules/@angular-devkit/build-angular/node_modules/@babel/traverse/lib/path/removal.js:32:8)
    at PluginPass.ForOfStatement (/home/mathias/src/nghyd/node_modules/@angular-devkit/build-angular/node_modules/@babel/plugin-transform-for-of/lib/index.js:195:16)
    at newFn (/home/mathias/src/nghyd/node_modules/@angular-devkit/build-angular/node_modules/@babel/traverse/lib/visitors.js:179:21)
    at NodePath._call (/home/mathias/src/nghyd/node_modules/@angular-devkit/build-angular/node_modules/@babel/traverse/lib/path/context.js:55:20)
    at NodePath.call (/home/mathias/src/nghyd/node_modules/@angular-devkit/build-angular/node_modules/@babel/traverse/lib/path/context.js:42:17)
    at NodePath.visit (/home/mathias/src/nghyd/node_modules/@angular-devkit/build-angular/node_modules/@babel/traverse/lib/path/context.js:90:31)
    at TraversalContext.visitQueue (/home/mathias/src/nghyd/node_modules/@angular-devkit/build-angular/node_modules/@babel/traverse/lib/context.js:112:16)
    at TraversalContext.visitSingle (/home/mathias/src/nghyd/node_modules/@angular-devkit/build-angular/node_modules/@babel/traverse/lib/context.js:84:19)
    at TraversalContext.visit (/home/mathias/src/nghyd/node_modules/@angular-devkit/build-angular/node_modules/@babel/traverse/lib/context.js:140:19)
    at Function.traverse.node (/home/mathias/src/nghyd/node_modules/@angular-devkit/build-angular/node_modules/@babel/traverse/lib/index.js:84:17)

The incriminated main-es2015.037c4c9354d91b8c3932.js temp file, even unminified, shows more than 400 for-of statements, hard to tell which one is responsible (but I'll review them anyway).

🌍 Your Environment


Angular CLI: 9.0.2
Node: 13.6.0
OS: linux x64

Angular: 9.0.1
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router
Ivy Workspace: Yes

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.900.2
@angular-devkit/build-angular     0.900.2
@angular-devkit/build-optimizer   0.900.2
@angular-devkit/build-webpack     0.900.2
@angular-devkit/core              9.0.2
@angular-devkit/schematics        9.0.2
@angular/cdk                      9.0.0
@angular/cli                      9.0.2
@angular/flex-layout              9.0.0-beta.29
@angular/material                 9.0.0
@ngtools/webpack                  9.0.2
@schematics/angular               9.0.2
@schematics/update                0.900.2
rxjs                              6.5.4
typescript                        3.7.5
webpack                           4.41.2

Anything else relevant?

tsconfig.app.json:

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/app",
    "baseUrl": "./",
    "types": [ "node" ],
    "typeRoots": [ "../node_modules/@types" ]
  },
  "angularCompilerOptions": {
    "enableIvy": false
  },
  "files": [
    "main.ts",
    "polyfills.ts"
  ]
}

tsconfig.json:

{
  "compileOnSave": false,
  "compilerOptions": {
    "downlevelIteration": true,
    "importHelpers": true,
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es2015",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2017",
      "dom"
    ],
    "module": "esnext",
    "baseUrl": "./"
  },
  "include": [
    "src/**/*.ts"
  ]
}

Thanks

blocked on upstream devkibuild-angular low regression triage #1 bufix

Most helpful comment

Hello, thanks for your answer.

cause of the problem

After some investigation, it happens that an if statement followed by a loop label is compiled in a way that Babel doesn't understand.

Source:

public isLinkedToResultOfNubs(nubs: Nub[], followLinksChain: boolean = true): boolean {
    let linked = false;
    if (this._valueMode === ParamValueMode.LINK && this.isReferenceDefined()) {
        const targetNubUid = this.referencedValue.nub.uid;
        if (this.referencedValue.isResult() || this.referencedValue.isExtraResult()) {
            outerloop:
            for (const y of nubs) {
                if (y.uid === targetNubUid) {
                    linked = true;
                    break outerloop;
                } else if (this.referencedValue.nub.dependsOnNubResult(y)) {
                    linked = true;
                    break outerloop;
                }
            }
        } else {
            if (followLinksChain) {
                linked = (this.referencedValue.element as ParamDefinition).isLinkedToResultOfNubs(nubs);
            }
        }
    }
    return linked;
}

Compiled es2015:

isLinkedToResultOfNubs(nubs, followLinksChain = !0) {
    let linked = !1;
    if (this._valueMode === ParamValueMode.LINK && this.isReferenceDefined()) {
        const targetNubUid = this.referencedValue.nub.uid;
        if (this.referencedValue.isResult() || this.referencedValue.isExtraResult()) outerloop: for (const y of nubs) {
            if (y.uid === targetNubUid) {
                linked = !0;
                break outerloop
            }
            if (this.referencedValue.nub.dependsOnNubResult(y)) {
                linked = !0;
                break outerloop
            }
        } else followLinksChain && (linked = this.referencedValue.element.isLinkedToResultOfNubs(nubs))
    }
    return linked
}

The second if that contains the loop has its braces removed, which is understandable as it contains only one expression. So it ends up being immediately followed by the loop label. This compiled es2015 version works in the browser.

But then Babel fails to translate it to es5 - failure confirmed with babel-cli on a file containing this function only.

Is if (foo) label: for (const b of bar) { in the generated code a valid syntax ? If it is, then I guess it's a Babel issue. If not, is it a Typescript issue ?

disclosure

Well… it happens that this code was poorly refactored at some time because the loop label is not needed here, so problem solved :) My bad. But it might still be a problem in other contexts.

Thanks

All 5 comments

This appears to be a bug but we will need to look at a reproduction to find and fix the problem. Can you setup a minimal reproduction please?

You can read here why this is needed. A good way to make a minimal reproduction is to create a new app via ng new repro-app and adding the minimum possible code to show the problem. Then you can push this repository to github and link it here.

Hello, thanks for your answer.

cause of the problem

After some investigation, it happens that an if statement followed by a loop label is compiled in a way that Babel doesn't understand.

Source:

public isLinkedToResultOfNubs(nubs: Nub[], followLinksChain: boolean = true): boolean {
    let linked = false;
    if (this._valueMode === ParamValueMode.LINK && this.isReferenceDefined()) {
        const targetNubUid = this.referencedValue.nub.uid;
        if (this.referencedValue.isResult() || this.referencedValue.isExtraResult()) {
            outerloop:
            for (const y of nubs) {
                if (y.uid === targetNubUid) {
                    linked = true;
                    break outerloop;
                } else if (this.referencedValue.nub.dependsOnNubResult(y)) {
                    linked = true;
                    break outerloop;
                }
            }
        } else {
            if (followLinksChain) {
                linked = (this.referencedValue.element as ParamDefinition).isLinkedToResultOfNubs(nubs);
            }
        }
    }
    return linked;
}

Compiled es2015:

isLinkedToResultOfNubs(nubs, followLinksChain = !0) {
    let linked = !1;
    if (this._valueMode === ParamValueMode.LINK && this.isReferenceDefined()) {
        const targetNubUid = this.referencedValue.nub.uid;
        if (this.referencedValue.isResult() || this.referencedValue.isExtraResult()) outerloop: for (const y of nubs) {
            if (y.uid === targetNubUid) {
                linked = !0;
                break outerloop
            }
            if (this.referencedValue.nub.dependsOnNubResult(y)) {
                linked = !0;
                break outerloop
            }
        } else followLinksChain && (linked = this.referencedValue.element.isLinkedToResultOfNubs(nubs))
    }
    return linked
}

The second if that contains the loop has its braces removed, which is understandable as it contains only one expression. So it ends up being immediately followed by the loop label. This compiled es2015 version works in the browser.

But then Babel fails to translate it to es5 - failure confirmed with babel-cli on a file containing this function only.

Is if (foo) label: for (const b of bar) { in the generated code a valid syntax ? If it is, then I guess it's a Babel issue. If not, is it a Typescript issue ?

disclosure

Well… it happens that this code was poorly refactored at some time because the loop label is not needed here, so problem solved :) My bad. But it might still be a problem in other contexts.

Thanks

Confirmed that loop labels also caused the same problem here. Changing the flow of code slightly allowed us to work around the problem, but this isn't ideal ;)

I get the exact same error as mentioned in the starting post:

Property body expected type of array but got null

And it appears that this issue is the only one mentioning this particular bug. However, I have no labled breakpoints. At least I find nothing with the regex break[^;].

So have there been any updates regarding this issue?

Edit: Angular version


ng --version

```bash
Angular CLI: 9.1.12
Node: 10.16.3
OS: linux x64

Angular: 9.1.12
... animations, cli, common, compiler, compiler-cli, core, forms
... language-service, localize, platform-browser
... platform-browser-dynamic, router
Ivy Workspace: Yes

Package Version

@angular-devkit/architect 0.901.12
@angular-devkit/build-angular 0.901.12
@angular-devkit/build-optimizer 0.901.12
@angular-devkit/build-webpack 0.901.12
@angular-devkit/core 9.1.12
@angular-devkit/schematics 9.1.12
@ngtools/webpack 9.1.12
@schematics/angular 9.1.12
@schematics/update 0.901.12
rxjs 6.5.5
typescript 3.8.3
webpack 4.42.0

```

Sorry, I was looking in the wrong place :)

We have a in-house library which was using loop labels. Refactored the code and now it works. Thanks a lot for your hint! Never would have found the issue without it.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ericel picture ericel  ·  3Comments

jmurphzyo picture jmurphzyo  ·  3Comments

gotschmarcel picture gotschmarcel  ·  3Comments

brtnshrdr picture brtnshrdr  ·  3Comments

hartjo picture hartjo  ·  3Comments