Skip to content

Add "use strict" directive#7411

Merged
jridgewell merged 14 commits intobabel:masterfrom
MarkusToe:add-use-strict-directive
Apr 10, 2018
Merged

Add "use strict" directive#7411
jridgewell merged 14 commits intobabel:masterfrom
MarkusToe:add-use-strict-directive

Conversation

@MarkusToe
Copy link
Copy Markdown
Contributor

Q                       A
Fixed Issues? Fixes #7349
Patch: Bug Fix?
Major: Breaking Change?
Minor: New Feature?
Tests Added + Pass? Yes
Documentation PR
Any Dependency Changes?
License MIT

I added the "use strict" directive in the function body BlockStatement.

@xtuc
Copy link
Copy Markdown
Member

xtuc commented Feb 22, 2018

Thanks for your pr!

Could you please add a test case where the parent already has the "use strict";?

"use strict";

class Foo {
  method(){

  }
}

should be

"use strict";

var Foo = function () {
  function Foo() {
    _classCallCheck(this, Foo);
  }

  _createClass(Foo, [{
    key: "method",
    value: function method() {}
  }]);

  return Foo;
}();

@MarkusToe
Copy link
Copy Markdown
Contributor Author

@xtuc I guess I would have to check if the program already has a "use strict"; directive? But I'm not sure where to look for it. I was trying to check this.node.directives around L165 but it is undefined. Do you have any tips for where to check if the program already has a "use strict"; directive?

@xtuc
Copy link
Copy Markdown
Member

xtuc commented Feb 23, 2018

I think we could do it with path.findParent(node => void), my understanding is that you need to check if one of your parent nodes already has a use strict directive (not only in the top-level scope).

closureParams,
t.blockStatement(body, [t.directive(t.directiveLiteral("use strict"))]),
);
}
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.

These two blocks looks really similar, maybe we could add on a condition for the injection of the directive.

Something like:

const directives = [];

if (parentHasUseStrict) {
    directives push t.directive(t.directiveLiteral("use strict"))
}

t.blockStatement(body, directives)

method() {

}
} No newline at end of file
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.

Test showing the case where the directive is already preset.+1:

Could you also please add a test where the directive isn't in the program but in a parent node? like that:

function t() {
  "use strict";
  class Foo {
    method() {}
  }
}

@babel-bot
Copy link
Copy Markdown
Collaborator

babel-bot commented Mar 1, 2018

Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/7543/

@babel-bot
Copy link
Copy Markdown
Collaborator

Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/7029/


if (!parentHasUseStrict) {
directives.push(t.directive(t.directiveLiteral("use strict")));
}
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.

Nit: we are matching two times the use strict string, we could construct the node t.directiveLiteral("use strict") before. Just an idea.

Copy link
Copy Markdown
Member

@xtuc xtuc left a comment

Choose a reason for hiding this comment

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

Nice job!

closureParams,
t.blockStatement(body, [t.directive(t.directiveLiteral("use strict"))]),
);
const parent = this.path.findParent((path) => path.isProgram() || path.isBlockStatement());
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.

wont it check only the nearest parent? we can have really deep tree and I guess we should check for something like:

const strictParent = this.path.findParent((path) => {
  if (!path.isProgram() && !path.isBlockStatement()) {
    return false
  }

  return path.node.directives.some(directive => directive.value.value === 'use strict')
});

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.

Yes that absolutely right! Thanks @Andarist

//
body.push(t.returnStatement(t.cloneNode(this.classRef)));

const parent = this.path.findParent(
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.

ive left comment regarding this here, it got hidden by github though (i think i might have commented on some older commit)

Copy link
Copy Markdown
Member

@xtuc xtuc left a comment

Choose a reason for hiding this comment

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

//
body.push(t.returnStatement(t.cloneNode(this.classRef)));

const strictParent = this.path.findParent(path => {
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.

looks better now 👍 would be great to add tests to cover this

also would be great to skip adding those directives in modules, but im not entirely sure how to check that, maybe you can check some property on the program or file? there are things like sourceType which could hint you this info, im not sure though where this info resides

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'm not sure this is actually testable. Having the directive in the program or in a parent function produces the same result.

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.

You can test by writing a random non-simple arguments list (destructuring, defaults) in the IIFE the transform generates and seeing if it compiles.

'use strict' inside a function is too late to disable sloppy mode in the arguments list.

Copy link
Copy Markdown
Member

@Andarist Andarist Mar 3, 2018

Choose a reason for hiding this comment

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

Not sure if we have understood each other - I was asking if there is a possibility to skip adding this for such input

import a from './a'
class B {
  constructor() { this.c = a() }
}

Modules are already strict be definition and they do not have to add use strict directive explicitly.

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.

Agreed. We'll want to make sure to avoid adding it to module. It should be safe to do

if (path.isProgram() && path.node.sourceType === "module") {
  return true;
}

to this to cover the module case.

Copy link
Copy Markdown
Member

@xtuc xtuc left a comment

Choose a reason for hiding this comment

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

lgtm with the new changes, thanks!

Good catch @Andarist

Copy link
Copy Markdown
Member

@nicolo-ribaudo nicolo-ribaudo left a comment

Choose a reason for hiding this comment

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

You also need to consider classes with only the onstructor (for which the iife isn't created):

// Input
class A {
  constructor() {
  }
}

// Current output
var A = function A() {
  _classCallCheck(this, A);
};

// Expected output
var A = function A() {
  "use strict";
  _classCallCheck(this, A);
};

@Jessidhia
Copy link
Copy Markdown
Member

We definitely need to always create an IIFE then. Otherwise, it won't be possible to have argument defaults or destructuring in the constructor's arguments.

@Andarist
Copy link
Copy Markdown
Member

Andarist commented Mar 6, 2018

@Kovensky could u give example of problematic input? I've looked into constructor with default argument case and I'm not sure why it wouldn't be possible to skip the IIFE in such case.

@loganfsmyth
Copy link
Copy Markdown
Member

loganfsmyth commented Mar 6, 2018

@Andarist For instance if we converted

class Foo {
  constructor(arg = {}) {}
}

to

let Foo = function(arg = {}) {
  "use strict";
};

that in fact throws an error because non-simple parameters need to already be strict by the time the parser reaches them, if you're going to try to make them strict.

We could likely skip the IIFE and keeping the simple function, as long as we special-case the non-simple function params to still create the IIFE.

@Andarist
Copy link
Copy Markdown
Member

Andarist commented Mar 6, 2018

Ok, that's an interesting case. As you said - we could try to continue optimizing this and de-opt on non-simple params when strict directive has to be added to the class (which in reality should be in minority of cases). I would be for optimizing it as it shouldn't be too complicated code-wise and I'm a little weirdo who likes optimized outputs 😅 dunno how you & other feel about such cases though.

Also I'm wondering why this:

let Foo = function(arg = {}) {};

doesn't throw? 🤔 Why it starts to throw once we add a strict directive in the function's body?

@loganfsmyth
Copy link
Copy Markdown
Member

Also I'm wondering why this: ... doesn't throw?

There's nothing wrong with having non-simple parameters when non-strict. The problem is that use strict in the body of a function affects the whole function, including the parameters. But because use strict is in the body of the function, there is a whole ton of work done by the parser before it has reached that point.

The issue essentially is that if your code is non-strict and you're parsing a whole function and then suddenly hit a use strict in the body, you'd have to rewind and redo all of the processing for the function a second time. For example

function fn(
  arg = function() {
    var let;
  }
) {
  "use strict";

}

when the parser encounters var let, should it error, or not? If use strict were allowed in the function body like this, it's impossible to say when the statement was parsed, because even if it isn't strict yet, it would be possible that later on it would suddenly become strict.

@Andarist
Copy link
Copy Markdown
Member

Andarist commented Mar 7, 2018

@loganfsmyth thanks for clarifying, makes sense 😄

@MarkusToe MarkusToe force-pushed the add-use-strict-directive branch 2 times, most recently from 9d3143d to 07b8409 Compare March 14, 2018 07:52
@MarkusToe
Copy link
Copy Markdown
Contributor Author

@loganfsmyth I am not sure how to handle the constructor only cases - could you please give me some guidance on where to start and what to check for?

So I guess at first we have to add the "use strict" directive to constructor-only function bodies, and then we should check for non-simple arguments and not add the "use strict" directive?

@Andarist
Copy link
Copy Markdown
Member

I think there are such cases:

  • already in strict mode - just create "only constructor"
  • not in strict, we need to add it
    a) simple arguments - add it inside the "only constructor"
    b) non-simple arguments - fallback, create IIFE with strict directive and return "only constructor"

@loganfsmyth loganfsmyth mentioned this pull request Apr 7, 2018
@loganfsmyth
Copy link
Copy Markdown
Member

How can I help us get this across the finish line? You've done some great work and I feel bad that it keep getting conflicts.

Markus Török added 3 commits April 9, 2018 21:07
@jridgewell jridgewell force-pushed the add-use-strict-directive branch from 1462b23 to 0ef10c8 Compare April 10, 2018 01:58
@jridgewell
Copy link
Copy Markdown
Member

Pushed a rebase, and included the constructor-only and non-simple parameter edge case.

@nicolo-ribaudo
Copy link
Copy Markdown
Member

@jridgewell Can you move the strictParent function to be reusable in the other PR? e.g. Maybe path.scope.isStrict().
Also, you should revert the changes to lerna.json

@jridgewell
Copy link
Copy Markdown
Member

Can you move the strictParent function to be reusable in the other PR? e.g. Maybe path.scope.isStrict().

Was already planning on it. Would you file an issue?

Also, you should revert the changes to lerna.json

Done.

@jridgewell jridgewell closed this Apr 10, 2018
@jridgewell jridgewell reopened this Apr 10, 2018
@jridgewell jridgewell merged commit 6597a47 into babel:master Apr 10, 2018
@jridgewell
Copy link
Copy Markdown
Member

Thanks for the core of the work @MarkusToe!

@MarkusToe
Copy link
Copy Markdown
Contributor Author

@jridgewell Thanks for finishing and I'm sorry that I didn't put more time in lately.

@loganfsmyth
Copy link
Copy Markdown
Member

@MarkusToe No problem at all. Thanks for getting the work on this moving in the first place!

@existentialism existentialism added the PR: New Feature 🚀 A type of pull request used for our changelog categories label Apr 12, 2018
@lock lock bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Oct 5, 2019
@lock lock bot locked as resolved and limited conversation to collaborators Oct 5, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

outdated A closed issue/PR that is archived due to age. Recommended to make a new issue PR: New Feature 🚀 A type of pull request used for our changelog categories

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ensure that class bodies have 'use strict' if the parent scope wasn't already

9 participants