chore: Migrate ESLint configuration file to new flat format. (#8675)

* chore: rename .eslintrc.js to eslint.config.js

* chore: Rename eslint.config.js to eslint.config.mjs.

* refactor: Migrate ESLint config to new flat format.

* chore: Remove old per-directory and global ignore ESLint config files.

* fix: Allowlist JSDoc tag aliases.

* fix: Don't require @license in tests/*.

* fix: Add NodeJS globals to several files that run under Node.

* chore: Remove now-unneeded ESLint directives in core.

* chore: Remove invalid/unneeded ESLint directives.

* fix: Fix invalid use of `await` outside of an `async` function.

* fix: Improve screenshot error message.

* fix: Update ESLint config file to not warn on existing violations.

* chore: Remove suppressions of rules that weren't triggering.

* chore: Fix package-lock.json.
This commit is contained in:
Aaron Dodson
2024-12-03 12:40:48 -08:00
committed by GitHub
parent 61bbd7dbf6
commit 5870c66cf0
22 changed files with 716 additions and 719 deletions

View File

@@ -1,28 +0,0 @@
# Build Artifacts
/msg/*
/build/*
/dist/*
/typings/*
/docs/*
# Tests other than mocha unit tests
/tests/blocks/*
/tests/themes/*
/tests/compile/*
/tests/jsunit/*
/tests/generators/*
/tests/mocha/webdriver.js
/tests/screenshot/*
/tests/test_runner.js
/tests/workspace_svg/*
# Demos, scripts, misc
/node_modules/*
/generators/*
/demos/*
/appengine/*
/externs/*
/closure/*
/scripts/gulpfiles/*
CHANGELOG.md
PULL_REQUEST_TEMPLATE.md

View File

@@ -1,189 +0,0 @@
const rules = {
'no-unused-vars': [
'error',
{
'args': 'after-used',
// Ignore vars starting with an underscore.
'varsIgnorePattern': '^_',
// Ignore arguments starting with an underscore.
'argsIgnorePattern': '^_',
},
],
// Blockly uses for exporting symbols. no-self-assign added in eslint 5.
'no-self-assign': ['off'],
// Blockly uses single quotes except for JSON blobs, which must use double
// quotes.
'quotes': ['off'],
// Blockly uses 'use strict' in files.
'strict': ['off'],
// Closure style allows redeclarations.
'no-redeclare': ['off'],
'valid-jsdoc': ['error'],
'no-console': ['off'],
'spaced-comment': [
'error',
'always',
{
'block': {
'balanced': true,
},
'exceptions': ['*'],
},
],
// Blockly uses prefixes for optional arguments and test-only functions.
'camelcase': [
'error',
{
'properties': 'never',
'allow': ['^opt_', '^_opt_', '^testOnly_'],
},
],
// Blockly uses capital letters for some non-constructor namespaces.
// Keep them for legacy reasons.
'new-cap': ['off'],
// Blockly uses objects as maps, but uses Object.create(null) to
// instantiate them.
'guard-for-in': ['off'],
'prefer-spread': ['off'],
};
/**
* Build shared settings for TS linting and add in the config differences.
* @return {Object} The override TS linting for given files and a given
* tsconfig.
*/
function buildTSOverride({files, tsconfig}) {
return {
'files': files,
'plugins': ['@typescript-eslint/eslint-plugin', 'jsdoc'],
'settings': {
'jsdoc': {
'mode': 'typescript',
},
},
'parser': '@typescript-eslint/parser',
'parserOptions': {
'project': tsconfig,
'tsconfigRootDir': '.',
'ecmaVersion': 2020,
'sourceType': 'module',
},
'extends': [
'plugin:@typescript-eslint/recommended',
'plugin:jsdoc/recommended',
'prettier', // Extend again so that these rules are applied last
],
'rules': {
// TS rules
// Blockly uses namespaces to do declaration merging in some cases.
'@typescript-eslint/no-namespace': ['off'],
// Use the updated TypeScript-specific rule.
'no-invalid-this': ['off'],
'@typescript-eslint/no-invalid-this': ['error'],
// Needs decision. 601 problems.
'@typescript-eslint/no-non-null-assertion': ['off'],
// Use TS-specific rule.
'no-unused-vars': ['off'],
'@typescript-eslint/no-unused-vars': [
'error',
{
'argsIgnorePattern': '^_',
'varsIgnorePattern': '^_',
},
],
// Temporarily disable. 23 problems.
'@typescript-eslint/no-explicit-any': ['off'],
// Temporarily disable. 128 problems.
'require-jsdoc': ['off'],
// Temporarily disable. 55 problems.
'@typescript-eslint/ban-types': ['off'],
// Temporarily disable. 33 problems.
'@typescript-eslint/no-empty-function': ['off'],
// Temporarily disable. 3 problems.
'@typescript-eslint/no-empty-interface': ['off'],
// We use this pattern extensively for block (e.g. controls_if) interfaces.
'@typescript-eslint/no-empty-object-type': ['off'],
// TsDoc rules (using JsDoc plugin)
// Disable built-in jsdoc verifier.
'valid-jsdoc': ['off'],
// Don't require types in params and returns docs.
'jsdoc/require-param-type': ['off'],
'jsdoc/require-returns-type': ['off'],
// params and returns docs are optional.
'jsdoc/require-param-description': ['off'],
'jsdoc/require-returns': ['off'],
// Disable for now (breaks on `this` which is not really a param).
'jsdoc/require-param': ['off'],
// Don't auto-add missing jsdoc. Only required on exported items.
'jsdoc/require-jsdoc': [
'warn',
{
'enableFixer': false,
'publicOnly': true,
},
],
'jsdoc/check-tag-names': [
'error',
{
'definedTags': [
'sealed',
'typeParam',
'remarks',
'define',
'nocollapse',
'suppress',
],
},
],
// Re-enable after Closure is removed. There shouldn't even be
// types in the TsDoc.
// These are "types" because of Closure's @suppress {warningName}
'jsdoc/no-undefined-types': ['off'],
'jsdoc/valid-types': ['off'],
// Disabled due to not handling `this`. If re-enabled,
// checkDestructured option
// should be left as false.
'jsdoc/check-param-names': ['off', {'checkDestructured': false}],
// Allow any text in the license tag. Other checks are not relevant.
'jsdoc/check-values': ['off'],
// Ensure there is a blank line between the body and any @tags,
// as required by the tsdoc spec (see #6353).
'jsdoc/tag-lines': ['error', 'any', {'startLines': 1}],
},
};
}
// NOTE: When this output is put directly in `module.exports`, the formatter
// does not align with the linter.
const eslintJSON = {
'rules': rules,
'env': {
'es2020': true,
'browser': true,
},
'globals': {
'goog': true,
'exports': true,
},
'extends': ['eslint:recommended', 'google', 'prettier'],
// TypeScript-specific config. Uses above rules plus these.
'overrides': [
buildTSOverride({
files: ['./**/*.ts', './**/*.tsx'],
tsconfig: './tsconfig.json',
}),
buildTSOverride({
files: ['./tests/typescript/**/*.ts', './tests/typescript/**/*.tsx'],
tsconfig: './tests/typescript/tsconfig.json',
}),
{
'files': ['./.eslintrc.js'],
'env': {
'node': true,
},
},
],
};
module.exports = eslintJSON;

View File

@@ -30,7 +30,6 @@ export interface ISerializer {
* state to record.
*/
save(workspace: Workspace): object | null;
/* eslint-enable valid-jsdoc */
/**
* Loads the state of the plugin or system.

View File

@@ -31,16 +31,12 @@ export class ConstantProvider extends BaseConstantProvider {
override getCSS_(selector: string) {
return super.getCSS_(selector).concat([
/* eslint-disable indent */
/* clang-format off */
// Insertion marker.
`${selector} .blocklyInsertionMarker>.blocklyPathLight,`,
`${selector} .blocklyInsertionMarker>.blocklyPathDark {`,
`fill-opacity: ${this.INSERTION_MARKER_OPACITY};`,
`stroke: none;`,
'}',
/* clang-format on */
/* eslint-enable indent */
]);
}
}

View File

@@ -786,7 +786,6 @@ export class ConstantProvider extends BaseConstantProvider {
override getCSS_(selector: string) {
return [
/* eslint-disable indent */
// Text.
`${selector} .blocklyText,`,
`${selector} .blocklyFlyoutLabelText {`,
@@ -871,4 +870,3 @@ export class ConstantProvider extends BaseConstantProvider {
];
}
}
/* eslint-enable indent */

View File

@@ -32,9 +32,6 @@ import {
import * as priorities from './priorities.js';
import * as serializationRegistry from './registry.js';
// TODO(#5160): Remove this once lint is fixed.
/* eslint-disable no-use-before-define */
/**
* Represents the state of a connection.
*/
@@ -795,7 +792,6 @@ const saveBlock = save;
export class BlockSerializer implements ISerializer {
priority: number;
/* eslint-disable-next-line require-jsdoc */
constructor() {
/** The priority for deserializing blocks. */
this.priority = priorities.BLOCKS;

View File

@@ -26,7 +26,6 @@ export interface State {
export class VariableSerializer implements ISerializer {
priority: number;
/* eslint-disable-next-line require-jsdoc */
constructor() {
/** The priority for deserializing variables. */
this.priority = priorities.VARIABLES;

View File

@@ -202,7 +202,6 @@ export function generateUniqueNameFromOptions(
let letterIndex = letters.indexOf(startChar);
let potName = startChar;
// eslint-disable-next-line no-constant-condition
while (true) {
let inUse = false;
for (let i = 0; i < usedNames.length; i++) {

View File

@@ -1077,14 +1077,12 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
* @internal
*/
updateScreenCalculationsIfScrolled() {
/* eslint-disable indent */
const currScroll = svgMath.getDocumentScroll();
if (!Coordinate.equals(this.lastRecordedPageScroll, currScroll)) {
this.lastRecordedPageScroll = currScroll;
this.updateScreenCalculations();
}
}
/* eslint-enable indent */
/**
* @returns The layer manager for this workspace.

308
eslint.config.mjs Normal file
View File

@@ -0,0 +1,308 @@
import eslint from '@eslint/js';
import googleStyle from 'eslint-config-google';
import jsdoc from 'eslint-plugin-jsdoc';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import globals from 'globals';
import tseslint from 'typescript-eslint';
// These rules are no longer supported, but the Google style package we depend
// on hasn't been updated in years to remove them, even though they have been
// removed from the repo. Manually delete them here to avoid breaking linting.
delete googleStyle.rules['valid-jsdoc'];
delete googleStyle.rules['require-jsdoc'];
const rules = {
'spaced-comment': [
'error',
'always',
{
'block': {
'balanced': true,
},
'exceptions': ['*'],
},
],
// Blockly uses prefixes for optional arguments and test-only functions.
'camelcase': [
'error',
{
'properties': 'never',
'allow': ['^opt_', '^_opt_', '^testOnly_'],
},
],
// Blockly uses capital letters for some non-constructor namespaces.
// Keep them for legacy reasons.
'new-cap': ['off'],
// Blockly uses objects as maps, but uses Object.create(null) to
// instantiate them.
'guard-for-in': ['off'],
};
/**
* Build shared settings for TS linting and add in the config differences.
* @param {object} root0 A configuration options struct.
* @param {!Array<string>} root0.files List of file globs to apply rules to.
* @param {string} root0.tsconfig Path to the tsconfig.json file to use.
* @returns {object} The override TS linting for given files and a given
* tsconfig.
*/
function buildTSOverride({files, tsconfig}) {
return {
files: files,
plugins: {
'@typescript-eslint': tseslint.plugin,
jsdoc,
},
languageOptions: {
parser: tseslint.parser,
'ecmaVersion': 2020,
'sourceType': 'module',
parserOptions: {
'project': tsconfig,
'tsconfigRootDir': '.',
},
globals: {
...globals.browser,
},
},
extends: [
...tseslint.configs.recommended,
jsdoc.configs['flat/recommended-typescript'],
eslintPluginPrettierRecommended,
],
rules: {
// TS rules
// Blockly uses namespaces to do declaration merging in some cases.
'@typescript-eslint/no-namespace': ['off'],
// Use the updated TypeScript-specific rule.
'no-invalid-this': ['off'],
'@typescript-eslint/no-invalid-this': ['error'],
'@typescript-eslint/no-unused-vars': [
'error',
{
'argsIgnorePattern': '^_',
'varsIgnorePattern': '^_',
},
],
// Temporarily disable. 23 problems.
'@typescript-eslint/no-explicit-any': ['off'],
// We use this pattern extensively for block (e.g. controls_if) interfaces.
'@typescript-eslint/no-empty-object-type': ['off'],
// params and returns docs are optional.
'jsdoc/require-param-description': ['off'],
'jsdoc/require-returns': ['off'],
// Disable for now (breaks on `this` which is not really a param).
'jsdoc/require-param': ['off'],
// Don't auto-add missing jsdoc. Only required on exported items.
'jsdoc/require-jsdoc': [
'warn',
{
'enableFixer': false,
'publicOnly': true,
},
],
'jsdoc/check-tag-names': [
'error',
{
'definedTags': [
'sealed',
'typeParam',
'remarks',
'define',
'nocollapse',
],
},
],
// Disabled due to not handling `this`. If re-enabled,
// checkDestructured option
// should be left as false.
'jsdoc/check-param-names': ['off', {'checkDestructured': false}],
// Allow any text in the license tag. Other checks are not relevant.
'jsdoc/check-values': ['off'],
// Ensure there is a blank line between the body and any @tags,
// as required by the tsdoc spec (see #6353).
'jsdoc/tag-lines': ['error', 'any', {'startLines': 1}],
},
};
}
export default [
{
// Note: there should be no other properties in this object
ignores: [
// Build artifacts
'msg/*',
'build/*',
'dist/*',
'typings/*',
'docs/*',
// Tests other than mocha unit tests
'tests/blocks/*',
'tests/themes/*',
'tests/compile/*',
'tests/jsunit/*',
'tests/generators/*',
'tests/mocha/webdriver.js',
'tests/screenshot/*',
'tests/test_runner.js',
'tests/workspace_svg/*',
// Demos, scripts, misc
'node_modules/*',
'generators/*',
'demos/*',
'appengine/*',
'externs/*',
'closure/*',
'scripts/gulpfiles/*',
'CHANGELOG.md',
'PULL_REQUEST_TEMPLATE.md',
],
},
eslint.configs.recommended,
jsdoc.configs['flat/recommended'],
googleStyle,
{
languageOptions: {
ecmaVersion: 2020,
sourceType: 'module',
},
settings: {
// Allowlist some JSDoc tag aliases we use.
'jsdoc': {
'tagNamePreference': {
'return': 'return',
'fileoverview': 'fileoverview',
'extends': 'extends',
'constructor': 'constructor',
},
},
},
rules,
},
{
files: [
'eslint.config.mjs',
'.prettierrc.js',
'gulpfile.js',
'scripts/helpers.js',
'tests/mocha/.mocharc.js',
'tests/migration/validate-renamings.mjs',
],
languageOptions: {
globals: {
...globals.node,
},
},
rules: {
'jsdoc/check-values': ['off'],
},
},
{
files: ['tests/**'],
languageOptions: {
globals: {
'Blockly': true,
'dartGenerator': true,
'javascriptGenerator': true,
'luaGenerator': true,
'phpGenerator': true,
'pythonGenerator': true,
},
},
rules: {
'jsdoc/check-values': ['off'],
'jsdoc/require-returns': ['off'],
'jsdoc/no-undefined-types': ['off'],
'jsdoc/valid-types': ['off'],
'jsdoc/check-types': ['off'],
'jsdoc/check-tag-names': ['warn', {'definedTags': ['record']}],
'jsdoc/tag-lines': ['off'],
'jsdoc/no-defaults': ['off'],
},
},
{
files: ['tests/browser/**'],
languageOptions: {
sourceType: 'module',
globals: {
'chai': false,
'sinon': false,
...globals.mocha,
...globals.browser,
...globals.node,
},
},
rules: {
// Allow uncommented helper functions in tests.
'jsdoc/require-jsdoc': ['off'],
'jsdoc/require-returns-type': ['off'],
'jsdoc/require-param-type': ['off'],
'no-invalid-this': ['off'],
},
},
{
files: ['tests/mocha/**'],
languageOptions: {
sourceType: 'module',
globals: {
'chai': false,
'sinon': false,
...globals.mocha,
...globals.browser,
},
},
rules: {
'no-unused-vars': ['off'],
// Allow uncommented helper functions in tests.
'jsdoc/require-jsdoc': ['off'],
'prefer-rest-params': ['off'],
'no-invalid-this': ['off'],
},
},
{
files: ['tests/node/**'],
languageOptions: {
globals: {
'console': true,
'require': true,
...globals.mocha,
...globals.node,
},
},
},
{
files: ['tests/playgrounds/**', 'tests/scripts/**'],
languageOptions: {
globals: {
...globals.browser,
},
},
},
{
files: ['scripts/**'],
languageOptions: {
globals: {
...globals.browser,
},
},
rules: {
'jsdoc/check-values': ['off'],
'jsdoc/require-returns': ['off'],
'jsdoc/tag-lines': ['off'],
},
},
...tseslint.config(
buildTSOverride({
files: ['**/*.ts', '**/*.tsx'],
tsconfig: './tsconfig.json',
}),
buildTSOverride({
files: ['tests/typescript/**/*.ts', 'tests/typescript/**/*.tsx'],
tsconfig: './tests/typescript/tsconfig.json',
}),
),
// Per the docs, this should be at the end because it disables rules that
// conflict with Prettier.
eslintPluginPrettierRecommended,
];

739
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -107,16 +107,16 @@
"@hyperjump/json-schema": "^1.5.0",
"@microsoft/api-documenter": "^7.22.4",
"@microsoft/api-extractor": "^7.29.5",
"@typescript-eslint/eslint-plugin": "^8.1.0",
"@typescript-eslint/parser": "^8.1.0",
"async-done": "^2.0.0",
"chai": "^5.1.1",
"concurrently": "^9.0.1",
"eslint": "^8.4.1",
"eslint": "^9.15.0",
"eslint-config-google": "^0.14.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-jsdoc": "^50.4.3",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jsdoc": "^50.5.0",
"eslint-plugin-prettier": "^5.2.1",
"glob": "^10.3.4",
"globals": "^15.12.0",
"google-closure-compiler": "^20240317.0.0",
"gulp": "^5.0.0",
"gulp-concat": "^2.6.1",
@@ -139,6 +139,7 @@
"readline-sync": "^1.4.10",
"rimraf": "^5.0.0",
"typescript": "^5.3.3",
"typescript-eslint": "^8.16.0",
"webdriverio": "^9.0.7",
"yargs": "^17.2.1"
},

View File

@@ -35,7 +35,7 @@ function posixPath(target) {
* @return {string} The value s as a eval-able string literal.
*/
function quote(str) {
/* eslint-disable no-control-regex, no-multi-spaces */
/* eslint-disable no-control-regex */
/** Regexp for characters to be escaped in a single-quoted string. */
const singleRE = /[\x00-\x1f\\\u2028\u2029']/g;
@@ -63,7 +63,7 @@ function quote(str) {
'\u2028': '\\u2028',
'\u2029': '\\u2029',
};
/* eslint-enable no-control-regex, no-multi-spaces */
/* eslint-enable no-control-regex */
return "'" + str.replace(singleRE, (c) => replacements[c]) + "'";
}

View File

@@ -1,10 +0,0 @@
{
"globals": {
"Blockly": true,
"dartGenerator": true,
"javascriptGenerator": true,
"luaGenerator": true,
"phpGenerator": true,
"pythonGenerator": true
}
}

View File

@@ -1,29 +0,0 @@
{
"env": {
"browser": true,
"mocha": true,
"node": true
},
"globals": {
"chai": false,
"sinon": false
},
"rules": {
"no-unused-vars": ["off"],
// Allow uncommented helper functions in tests.
"require-jsdoc": ["off"],
"prefer-rest-params": ["off"],
"no-invalid-this": ["off"],
"valid-jsdoc": [
"error",
{
"requireReturnType": false,
"requireParamType": false
}
]
},
"extends": "../../.eslintrc.js",
"parserOptions": {
"sourceType": "module"
}
}

View File

@@ -25,38 +25,39 @@ const RENAMINGS_URL = new URL(
import.meta.url,
);
const renamingsJson5 = await readFile(RENAMINGS_URL);
const renamings = JSON5.parse(renamingsJson5);
readFile(RENAMINGS_URL).then((renamingsJson5) => {
const renamings = JSON5.parse(renamingsJson5);
const output = await validate(SCHEMA_URL, renamings, BASIC);
if (!output.valid) {
console.error(`Renamings file is invalid. First error occurs at:
validate(SCHEMA_URL, renamings, BASIC).then((output) => {
if (!output.valid) {
console.error(`Renamings file is invalid. First error occurs at:
${output.errors[0].instanceLocation}`);
console.info(
`Here is the full validator output, in case that helps:\n`,
output,
);
process.exit(1);
}
// File passed schema validation. Do some additional checks.
let ok = true;
Object.entries(renamings).forEach(([version, modules]) => {
// Scan through modules and check for duplicates.
const seen = new Set();
for (const {oldName} of modules) {
if (seen.has(oldName)) {
console.error(
`Duplicate entry for module ${oldName} ` + `in version ${version}.`,
console.info(
`Here is the full validator output, in case that helps:\n`,
output,
);
ok = false;
process.exit(1);
}
seen.add(oldName);
}
// File passed schema validation. Do some additional checks.
let ok = true;
Object.entries(renamings).forEach(([version, modules]) => {
// Scan through modules and check for duplicates.
const seen = new Set();
for (const {oldName} of modules) {
if (seen.has(oldName)) {
console.error(
`Duplicate entry for module ${oldName} ` + `in version ${version}.`,
);
ok = false;
}
seen.add(oldName);
}
});
if (!ok) {
console.error('Renamings file is invalid.');
process.exit(1);
}
// Default is a successful exit 0.
});
});
if (!ok) {
console.error('Renamings file is invalid.');
process.exit(1);
}
// Default is a successful exit 0.

View File

@@ -1,21 +0,0 @@
{
"env": {
"browser": true,
"mocha": true
},
"globals": {
"chai": false,
"sinon": false
},
"rules": {
"no-unused-vars": ["off"],
// Allow uncommented helper functions in tests.
"require-jsdoc": ["off"],
"prefer-rest-params": ["off"],
"no-invalid-this": ["off"]
},
"extends": "../../.eslintrc.js",
"parserOptions": {
"sourceType": "module"
}
}

View File

@@ -484,7 +484,6 @@ Serializer.Fields.TextInput.Simple = new SerializerTestCase(
'</block>' +
'</xml>',
);
/* eslint-disable no-tabs */
Serializer.Fields.TextInput.Tabs = new SerializerTestCase(
'Tabs',
'<xml xmlns="https://developers.google.com/blockly/xml">' +
@@ -493,7 +492,6 @@ Serializer.Fields.TextInput.Tabs = new SerializerTestCase(
'</block>' +
'</xml>',
);
/* eslint-enable no-tabs */
Serializer.Fields.TextInput.Symbols = new SerializerTestCase(
'Symbols',
'<xml xmlns="https://developers.google.com/blockly/xml">' +
@@ -621,7 +619,6 @@ Serializer.Fields.Variable.Types = new SerializerTestCase(
'</block>' +
'</xml>',
);
/* eslint-disable no-tabs */
Serializer.Fields.Variable.Tabs = new SerializerTestCase(
'Tabs',
'<xml xmlns="https://developers.google.com/blockly/xml">' +
@@ -633,7 +630,6 @@ Serializer.Fields.Variable.Tabs = new SerializerTestCase(
'</block>' +
'</xml>',
);
/* eslint-enable no-tabs */
Serializer.Fields.Variable.Symbols = new SerializerTestCase(
'Symbols',
'<xml xmlns="https://developers.google.com/blockly/xml">' +

View File

@@ -1,4 +1,3 @@
/* eslint-disable valid-jsdoc */
/**
* @license
* Copyright 2020 Google LLC

View File

@@ -20,6 +20,5 @@ suite('Workspace', function () {
sharedTestTeardown.call(this);
});
// eslint-disable-next-line no-use-before-define
testAWorkspace();
});

View File

@@ -1,12 +0,0 @@
{
"env": {
"node": true,
"browser": false,
"mocha": true
},
"globals": {
"console": true,
"require": true
},
"extends": "../../.eslintrc.js"
}

View File

@@ -40,7 +40,7 @@ function svgToPng_(data, width, height, callback) {
const dataUri = canvas.toDataURL('image/png');
callback(dataUri);
} catch (err) {
console.warn('Error converting the workspace svg to a png');
console.warn('Error converting the workspace svg to a png: ' + err);
callback('');
}
};