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: Fix compilation errors under Closure's strict mode #6073

Merged
merged 9 commits into from
Apr 19, 2022

Conversation

gonfunko
Copy link
Contributor

The basics

  • I branched from develop
  • My pull request is against develop
  • My code follows the style guide
  • I ran npm run format and npm run lint

The details

Resolves

Progress towards #5857

Proposed Changes

This PR resolves many compilation errors in core when run under Closure's strict mode.

Reason for Changes

Blockly needs to compile cleanly under strict mode before it can be migrated to TS.

Test Coverage

Test suite passes.

@gonfunko gonfunko requested a review from a team as a code owner April 12, 2022 18:21
@gonfunko gonfunko changed the title Strict cleanup fix: Fix compilation errors under Closure's strict mode Apr 12, 2022
Copy link
Collaborator

@rachel-fenichel rachel-fenichel left a comment

Choose a reason for hiding this comment

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

Phew! There's a lot here.

core/block_animations.js Outdated Show resolved Hide resolved
const percent = ms / DURATION;

if (percent > 1) {
group.skew_ = '';
group.setAttribute('data-skew', '');
Copy link
Collaborator

Choose a reason for hiding this comment

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

It looks like you're just storing the skew data on the DOM between steps. Writing to and reading from the DOM can be slow.

Please confirm that this is the same number of writes as the previous code. In future we can clean it up to use local variables instead of storing data on the DOM.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should look for an alternate solution here for another reason as well.

The core issue here is type safety. By setting and accessing random properties on an otherwise well-defined type, we are ignoring type safety. Moving those random properties to a data attribute does not solve the type safety issue, it just moves it to a place where Closure (and eventually TypeScript) cannot yell at us about the issue.

So I would prefer to see an alternate solution such as using a local variable / additional parameter (if this property is not used outside this class), or otherwise defining an actually useful type. In TypeScript there is the intersection type which seems fine for this (see this example of adding the honey property to an Animal: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces).

If we are just going to disable the type safety checking anyway then we might as well just cast to any instead of using the dom to avoid the check. That would at least be trackable / lintable and easier to follow up on later to make improvements.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for calling this out. This definitely does write to the DOM more, and you're also quite right that this doesn't actually fix things from a type safety perspective. It seems like I could just use a local variable for skew that gets passed to the recursive function call, but the skew also seems to be used when when dragging a block or comment in a workspace without a drag surface. From a cursory glance I think removing it means that the wiggle wouldn't happen on a drag surface-less workspace; perhaps that's enough of an edge case we can live with it? If not I'll probably opt for the any (well, ? for now) cast as you suggested.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Go for any to avoid changes in behaviour, but file an issue to follow up and mark it as a P1 for this quarter.

core/block_dragger.js Outdated Show resolved Hide resolved
core/field_dropdown.js Show resolved Hide resolved
core/insertion_marker_manager.js Outdated Show resolved Hide resolved
@@ -296,13 +298,15 @@ class RenderedConnection extends Connection {
// Vertical line, puzzle tab, vertical line.
const yLen = renderConstants.TAB_OFFSET_FROM_TOP;
steps = svgPaths.moveBy(0, -yLen) + svgPaths.lineOnAxis('v', yLen) +
shape.pathDown + svgPaths.lineOnAxis('v', yLen);
(/** @type {PuzzleTab} */ (shape)).pathDown +
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is tricky. It's not guaranteed to be a puzzle tab. It's just guaranteed to have a pathDown property. Same issue with the notch.

@@ -1237,13 +1237,13 @@ class WorkspaceSvg extends Workspace {
this.cachedParentSvgSize_.width = width;
// This is set to support the public (but deprecated) Blockly.svgSize
// method.
svg.cachedWidth_ = width;
Copy link
Collaborator

Choose a reason for hiding this comment

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

TODO as a followup: delete svgSize and everything associated. Its deprecation window has passed, but deletion should be a standalone PR.

Copy link
Contributor

@maribethb maribethb left a comment

Choose a reason for hiding this comment

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

So this is a lot of work and I definitely respect that, so feel free to push back on this feedback.

But in general I would prefer to see fewer casts, and instead choose one of the following as appropriate

  1. Better type choice from the beginning (like you already did by changing Element to HTMLElement, etc) or at the source
  2. More type refinement checks instead (check if the thing is an instanceof another thing, then Closure and TypeScript compilers both know how to handle that smartly with no casts)
  3. some special cases could probably use more general improvements, like checking the type instead of using isCollapsible, but i think those cases are more for improving in the future rather than changing here.

casts will still be necessary and fine in some cases (e.g. the document.createElement calls that can't statically return the correct / more narrow type) but there are others where I think we could improve, though again I acknowledge that is more work and probably requires digging in on a more case-by-case basis.

also I didn't catch all of them but all of the casts to non-primitive types should have nullability modifiers. i think we must have a setting turned off to do strict null checks because otherwise closure should definitely complain when you cast something to implicitly nullable (with no modifier) and then immediately assume it's actually not null. but i think this will cause problems because (hopefully) TypeScript will yell about that, as it should.

const percent = ms / DURATION;

if (percent > 1) {
group.skew_ = '';
group.setAttribute('data-skew', '');
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should look for an alternate solution here for another reason as well.

The core issue here is type safety. By setting and accessing random properties on an otherwise well-defined type, we are ignoring type safety. Moving those random properties to a data attribute does not solve the type safety issue, it just moves it to a place where Closure (and eventually TypeScript) cannot yell at us about the issue.

So I would prefer to see an alternate solution such as using a local variable / additional parameter (if this property is not used outside this class), or otherwise defining an actually useful type. In TypeScript there is the intersection type which seems fine for this (see this example of adding the honey property to an Animal: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces).

If we are just going to disable the type safety checking anyway then we might as well just cast to any instead of using the dom to avoid the check. That would at least be trackable / lintable and easier to follow up on later to make improvements.

core/browser_events.js Outdated Show resolved Hide resolved
core/bubble.js Outdated Show resolved Hide resolved
if (this.draggingBubble_.isComment) {
// TODO (adodson): Resolve build errors when requiring
// WorkspaceCommentSvg.
if (this.draggingBubble_ instanceof WorkspaceCommentSvg) {
const event = /** @type {!CommentMove} */
(new (eventUtils.get(eventUtils.COMMENT_MOVE))(
/** @type {!WorkspaceCommentSvg} */ (this.draggingBubble_)));
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think you should need this cast either inside your new type refinement check. also it's weird to me that (new (eventUtils.get(eventUtils.COMMENT_MOVE)) would need to be cast... why doesn't that just return the correct type?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, thanks! The CommentMove cast appears to be necessary because eventUtils.get returns the Abstract event class. I agree it would be nice if it used generics but that'd be a breaking change and probably better suited for post-TS cleanup?

Copy link
Collaborator

Choose a reason for hiding this comment

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

core/clipboard.js Outdated Show resolved Hide resolved
core/renderers/common/drawer.js Outdated Show resolved Hide resolved
@@ -186,7 +191,7 @@ class Drawer {
this.positionExternalValueConnection_(row);

const pathDown = (typeof input.shape.pathDown === 'function') ?
input.shape.pathDown(input.height) :
(/** @type {function(number)} */ (input.shape.pathDown))(input.height) :
Copy link
Contributor

Choose a reason for hiding this comment

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

this one feels weird to me too. i don't feel i've seen a typecast just so we can tell it what type the parameter will be. seems like closure knowing it's a function should be enough but i guess not.

core/toolbox/category.js Show resolved Hide resolved
core/toolbox/toolbox.js Outdated Show resolved Hide resolved
core/xml.js Outdated Show resolved Hide resolved
@rachel-fenichel
Copy link
Collaborator

@maribethb -- I agree with your points where we can use instanceof and rely on type narrowing.

Where larger changes are required, including for type safety, I prefer to file issues and mark them as P1 for this quarter.

The internal typescript migration guide says:

Preserve runtime behavior.

Be aware that the .js files returned after TypeScript compilation aren't guaranteed to be a character-for-character equal to the original – though they should behave the same. If you create .ts files during the migration that violate that original intent, you would be violating the safety of the migration.

Don't fix bugs or do cleanups during the migration.

A TypeScript migration forces you to examine every single file in your codebase. In each file, you'll touch 30% to 50% of the lines. Though you'll likely see issues that you want to fix, don't clean up things now.

Fixing bugs during migration violates the "preserve runtime behavior" principle. Most migrations are done on codebases that are already in production and working adequately well.

Suppressing new compiler errors is OK.

The TypeScript compiler is often stricter. Unfortunately, proper fixes for new errors often means different runtime behavior. Instead of proper fixes, choose suppressions like go/any_during_migration. Make sure that the suppressions are easy to search and find so they can be properly fixed after the TypeScript migration.

If Aaron can get these errors out of the way, it lets him find the next blocker for migration, and we need to find those as early as possible. I can follow up and do cleanup in separate PRs--which also makes it easier to undo if the cleanup changes runtime behaviour and causes bugs.

Copy link
Collaborator

@rachel-fenichel rachel-fenichel left a comment

Choose a reason for hiding this comment

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

This looks better. Please add ! to types in zelos/drawer.js, measurables/inline_input.js, common/marker_svg.js, common/drawer.js, and zelos/info.js.

core/flyout_base.js Outdated Show resolved Hide resolved
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.

3 participants