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

[Web] Update to eslint 9 #91771

Closed
wants to merge 1 commit into from

Conversation

Faless
Copy link
Collaborator

@Faless Faless commented May 9, 2024

Draft since it needs more work to match the old (stricter) rules.

@patwork
Copy link
Contributor

patwork commented May 9, 2024

Note, in flat config mode for eslint 9+ you can create one shared config like this (not finished example):

import globals from "globals";
import pluginJs from "@eslint/js";
import pluginReference from "eslint-plugin-html";

export default [
	pluginJs.configs.recommended,
	// pluginJs.configs.all,

	{
		rules: {
			"indent": ["error", "tab"],
			"curly": ["error", "all"],
			"quote-props": ["error", "consistent"],
			"no-self-assign": "off",
			"no-unused-vars": "warn", // FIXME
		},
	},

	{
		files: ["js/engine/**/*.js"],
		languageOptions: {
			globals: {
				...globals.browser,
				"Features": true,
				"Godot": true,
				"InternalConfig": true,
				"Preloader": true,
			},
		},
	},

	{
		files: ["js/jsdoc2rst/**/*.js"],
		languageOptions: {
			globals: globals.node,
		},
	},

	{
		files: ["js/libs/**/*.js"],
		languageOptions: {
			globals: {
				...globals.browser,
				"autoAddDeps": true,
				"Browser": true,
				"ERRNO_CODES": true,
				"FS": true,
				"GL": true,
				"GodotConfig": true,
				"GodotEventListeners": true,
				"GodotFS": true,
				"GodotOS": true,
				"GodotRuntime": true,
				"HEAP32": true,
				"HEAP8": true,
				"HEAPF32": true,
				"HEAPU8": true,
				"IDBFS": true,
				"IDHandler": true,
				"LibraryManager": true,
				"mergeInto": true,
				"XRWebGLLayer": true,
			},
		},
	},

	{
		files: ["html/**/*.js"],
		languageOptions: {
			globals: {
				...globals.browser,
				"___GODOT_CACHE___": true,
				"___GODOT_ENSURE_CROSSORIGIN_ISOLATION_HEADERS___": true,
				"___GODOT_OPT_CACHE___": true,
				"onClientMessage": true,
			},
		},
	},

	{
		files: ["html/**/*.html"],
		plugins: {
			"eslint-plugin-html": pluginReference,
		},
		languageOptions: {
			globals: {
				...globals.browser,
				"Engine": true,
				"$GODOT_CONFIG": true,
				"$GODOT_PROJECT_NAME": true,
				"$GODOT_THREADS_ENABLED": true,
				"___GODOT_THREADS_ENABLED___": true,
			},
		},
	},

	{
		ignores: [
			"eslint.config.mjs",
			"**/*.externs.js",
		],
	},
];

@Faless
Copy link
Collaborator Author

Faless commented May 9, 2024

@patwork amazing, thanks a lot 💙 !

I think no-unused-vars should be:

"no-unused-vars": ["error", { "args": "none", "caughtErrors": "none" }]

@Faless Faless marked this pull request as ready for review May 9, 2024 17:36
@Faless Faless requested review from a team as code owners May 9, 2024 17:36
@Faless Faless force-pushed the web/eslint9_prerequisite branch from 8f5ec0c to 2864086 Compare May 9, 2024 17:39
@Faless Faless changed the title [Web] Always run eslint from a parent folder of the scanned files [Web] Update to eslint 9 May 9, 2024
@Faless Faless marked this pull request as draft May 9, 2024 17:47
@Faless
Copy link
Collaborator Author

Faless commented May 9, 2024

As discussed with @patwork this needs some more work to match the old (stricter) rules.

I'm leaving this PR in draft as a base for future work.

Comment on lines +9 to +14
"lint:engine": "cd ../../ && eslint \"platform/web/js/engine/*.js\" --no-config-lookup -c platform/web/eslint.config.mjs",
"lint:libs": "cd ../../ && eslint \"platform/web/js/libs/*.js\" --no-config-lookup -c platform/web/eslint.config.mjs",
"lint:tools": "cd ../../ && eslint \"platform/web/js/jsdoc2rst/**/*.js\" --no-config-lookup -c platform/web/eslint.config.mjs",
"lint:modules": "cd ../../ && eslint \"modules/**/*.js\" --no-config-lookup -c platform/web/eslint.config.mjs",
"lint:sw": "cd ../../ && eslint \"misc/dist/html/service-worker.js\" --no-config-lookup -c platform/web/eslint.config.mjs",
"lint:html": "cd ../../ && eslint \"misc/dist/html/*.html\" --no-config-lookup -c platform/web/eslint.config.mjs",
Copy link
Collaborator Author

@Faless Faless May 9, 2024

Choose a reason for hiding this comment

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

We could probably now unify this as a single command I think:

cd ../../ && eslint \"platform/web/js/engine/*.js\" \"platform/web/js/libs/*.js\" \"platform/web/js/jsdoc2rst/**/*.js\" \"modules/**/*.js\" \"misc/dist/html/service-worker.js\" \"misc/dist/html/*.html\" --no-config-lookup -c platform/web/eslint.config.mjs

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Or even:

cd ../../ && eslint \"platform/web/**/*.js\" \"modules/**/*.js\" \"misc/dist/html/*.js\" \"misc/dist/html/*.html\" --no-config-lookup -c platform/web/eslint.config.mjs

@AThousandShips AThousandShips added this to the 4.x milestone May 10, 2024
@patwork
Copy link
Contributor

patwork commented May 10, 2024

OK, so this is WIP eslint 9+ flat config with rules.recommended. Next step will be to adapt it to rules.all:

import fs from "fs";
import globals from "globals";
import pluginJs from "@eslint/js";
import htmlPlugin from "@html-eslint/eslint-plugin";
import htmlParser from "@html-eslint/parser";
import pluginReference from "eslint-plugin-html";

// eslint 9+ flat config shenanigans
if (!fs.existsSync("./platform/web/eslint.config.mjs")) {
	throw Error("eslint must be run from the Godot project root folder");
}

export default [
	pluginJs.configs.recommended, // FIXME this should be pluginJs.configs.all

	// common rules for all files
	{
		rules: {
			"indent": ["error", "tab"],
			"curly": ["error", "all"],
			"quote-props": ["error", "consistent"],
			"no-self-assign": "off",
			"no-unused-vars": ["error", { "args": "none", "caughtErrors": "none" }],
		},
	},

	// jsdoc2rst (node)
	{
		files: ["platform/web/js/jsdoc2rst/**/*.js"],
		languageOptions: {
			globals: globals.node,
		},
	},

	// engine files (browser)
	{
		files: ["platform/web/js/engine/**/*.js"],
		languageOptions: {
			globals: {
				...globals.browser,
				"Features": true,
				"Godot": true,
				"InternalConfig": true,
				"Preloader": true,
			},
		},
	},

	// libraries and modules (browser)
	{
		files: [
			"platform/web/js/libs/**/*.js",
			"modules/**/*.js"
		],
		languageOptions: {
			globals: {
				...globals.browser,
				"autoAddDeps": true,
				"Browser": true,
				"ERRNO_CODES": true,
				"FS": true,
				"GL": true,
				"GodotConfig": true,
				"GodotEventListeners": true,
				"GodotFS": true,
				"GodotOS": true,
				"GodotRuntime": true,
				"HEAP32": true,
				"HEAP8": true,
				"HEAPF32": true,
				"HEAPU8": true,
				"IDBFS": true,
				"IDHandler": true,
				"LibraryManager": true,
				"mergeInto": true,
				"XRWebGLLayer": true,
			},
		},
	},

	// javascript templates (service workers)
	{
		files: ["misc/dist/html/**/*.js"],
		languageOptions: {
			globals: {
				...globals.browser,
				"___GODOT_CACHE___": true,
				"___GODOT_ENSURE_CROSSORIGIN_ISOLATION_HEADERS___": true,
				"___GODOT_OPT_CACHE___": true,
				"onClientMessage": true, // FIXME WHAT IS THIS?
			},
		},
	},

	// html templates
	{
		files: ["misc/dist/html/**/*.html"],
		plugins: {
			"@html-eslint": htmlPlugin,
			"eslint-plugin-html": pluginReference,
		},
		languageOptions: {
			parser: htmlParser,
			globals: {
				...globals.browser,
				"Engine": true,
				"$GODOT_CONFIG": true,
				"$GODOT_PROJECT_NAME": true,
				"$GODOT_THREADS_ENABLED": true,
				"___GODOT_THREADS_ENABLED___": true,
			},
		},
		rules: {
			...htmlPlugin.configs.recommended.rules,
			"@html-eslint/indent": ["error", "tab"],
			"@html-eslint/require-closing-tags": ["error", { "selfClosing": "never" }],
		},
	},

	// ignored files
	{
		ignores: [
			"**/eslint.config.mjs",
			"**/.eslintrc*.js",
			"**/*.externs.js",
		],
	},
];

@patwork
Copy link
Contributor

patwork commented May 10, 2024

  1. I was wondering what is "onClientMessage" in service worker? I could not find documentation for such a function anywhere.

  2. eslint upgrade will also require the removal of "eslint-disable" directives in several javascript files

  3. I was wondering whether, instead of ignoring errors in catch(), to add in each one something like console.warn(e)

  4. thanks to flat config, scripts in package.json can be simplified:

"scripts": {
  "docs": "jsdoc --template js/jsdoc2rst/ js/engine/engine.js js/engine/config.js js/engine/features.js --destination ''",
  "lint": "cd ../.. && eslint --no-config-lookup --config ./platform/web/eslint.config.mjs ./platform/web ./modules ./misc/dist/html",
  "fix": "cd ../.. && eslint --fix --no-config-lookup --config ./platform/web/eslint.config.mjs ./platform/web ./modules ./misc/dist/html",
},

@patwork
Copy link
Contributor

patwork commented May 10, 2024

pluginJs.configs.all is crazy, with its default set of rules eslint finds 4400+ problems:

   1366 camelcase
    479 no-magic-numbers
    467 func-names
    370 sort-keys
    301 object-shorthand
    277 one-var
    129 id-length
    127 prefer-arrow-callback
    118 capitalized-comments
     94 dot-notation
     71 func-style
     67 max-statements
     59 no-inline-comments
     49 no-unused-vars
     47 no-ternary
     47 max-params
     44 no-plusplus
     43 line-comment-position
     39 prefer-destructuring
     31 no-underscore-dangle
     29 no-undefined
     23 multiline-comment-style
     15 no-bitwise
     14 max-lines-per-function
     11 no-negated-condition
     11 max-lines
      7 no-self-assign
      6 no-implicit-coercion
      6 no-eq-null
      6 eqeqeq
      5 prefer-rest-params
      5 init-declarations
      4 require-unicode-regexp
      4 prefer-spread
      4 prefer-named-capture-group
      4 no-useless-assignment
      4 no-console
      4 consistent-this
      3 new-cap
      2 prefer-promise-reject-errors
      2 no-warning-comments
      2 no-empty-function
      1 no-continue
      1 max-classes-per-file
      1 func-name-matching
      1 complexity

@Faless
Copy link
Collaborator Author

Faless commented May 10, 2024

  1. I was wondering what is "onClientMessage" in service worker? I could not find documentation for such a function anywhere.

I actually think that's a bug (left-over dead code using undefined symbols).

2. eslint upgrade will also require the removal of "eslint-disable" directives in several javascript files

Didn't we want to try to stay consistent with the rules? In that case most eslint-disable exceptions should be kept I think.
Happy to discuss which one requires changing.

3. I was wondering whether, instead of ignoring errors in catch(), to add in each one something like console.warn(e)

Again, not sure where specifically, but some catch may be totally fine to fail, especially in engine code where we don't want to spam WARN_PRINT, and printing to console via emcc is actually not great in terms of performances.

4. thanks to flat config, scripts in package.json can be simplified:

Looking great!

@patwork
Copy link
Contributor

patwork commented May 10, 2024

The thing is that config.all is more picky than the previous airbnb-base. After restoring all the old rules we're left with:

479 no-magic-numbers
370 sort-keys
277 one-var
129 id-length
118 capitalized-comments
59 no-inline-comments
47 no-ternary
47 max-params
43 line-comment-position
29 no-undefined
23 multiline-comment-style
11 no-negated-condition
6 no-implicit-coercion
6 no-eq-null
6 eqeqeq
5 init-declarations
4 require-unicode-regexp
4 prefer-named-capture-group
4 no-useless-assignment
4 consistent-this
3 new-cap
2 prefer-promise-reject-errors
2 no-warning-comments
2 no-empty-function
1 func-name-matching
1 complexity

It's worth considering which ones are worth leaving enabled or can be disabled directly in the javascript code.

Also found two unnecessary eslint-disable:

modules/webxr/native/library_godot_webxr.js
295:62 warning Unused eslint-disable directive (no problems were reported from 'no-undef')

platform/web/js/engine/features.js
1:20 warning Unused eslint-disable directive (no problems were reported from 'no-unused-vars')

@patwork
Copy link
Contributor

patwork commented May 11, 2024

I think the first 13 rules that are repeated many times in the sources can be safely excluded. The others can be considered, as some of them may actually make it easier to avoid potential errors.

Rule no-eq-null

/misc/dist/html/full-size.html
  153:10  error  Use '===' to compare with null  no-eq-null

/misc/dist/html/service-worker.js
   73:6  error  Use '===' to compare with null  no-eq-null
  125:9  error  Use '===' to compare with null  no-eq-null

/platform/web/js/engine/engine.js
   44:7  error  Use '===' to compare with null  no-eq-null
   81:9  error  Use '===' to compare with null  no-eq-null
  225:9  error  Use '===' to compare with null  no-eq-null

✖ 6 problems (6 errors, 0 warnings)

Rule eqeqeq

/misc/dist/html/full-size.html
  153:23  error  Expected '!==' and instead saw '!='  eqeqeq

/misc/dist/html/service-worker.js
   73:15  error  Expected '===' and instead saw '=='  eqeqeq
  125:16  error  Expected '!==' and instead saw '!='  eqeqeq

/platform/web/js/engine/engine.js
   44:19  error  Expected '===' and instead saw '=='  eqeqeq
   81:21  error  Expected '===' and instead saw '=='  eqeqeq
  225:20  error  Expected '===' and instead saw '=='  eqeqeq

✖ 6 problems (6 errors, 0 warnings)

Rule init-declarations

/misc/dist/html/editor.html
  421:5  error  Variable 'setStatusMode' should be initialized on declaration    init-declarations
  422:5  error  Variable 'setStatusNotice' should be initialized on declaration  init-declarations

/modules/webrtc/library_godot_webrtc.js
  52:9  error  Variable 'buffer' should be initialized on declaration  init-declarations

/modules/websocket/library_godot_websocket.js
  52:8  error  Variable 'buffer' should be initialized on declaration  init-declarations

/modules/webxr/native/library_godot_webxr.js
  417:7  error  Variable 'matrix' should be initialized on declaration  init-declarations

✖ 5 problems (5 errors, 0 warnings)

Rule require-unicode-regexp

/platform/web/js/libs/library_godot_input.js
  243:17  error  Use the 'u' flag  require-unicode-regexp
  245:17  error  Use the 'u' flag  require-unicode-regexp
  419:25  error  Use the 'u' flag  require-unicode-regexp
  420:25  error  Use the 'u' flag  require-unicode-regexp

✖ 4 problems (4 errors, 0 warnings)

Rule prefer-named-capture-group

/platform/web/js/libs/library_godot_input.js
  243:17  error  Capture group '([0-9a-f]{4})' should be converted to a named or non-capturing group  prefer-named-capture-group
  243:17  error  Capture group '([0-9a-f]{4})' should be converted to a named or non-capturing group  prefer-named-capture-group
  245:17  error  Capture group '([0-9a-f]+)' should be converted to a named or non-capturing group    prefer-named-capture-group
  245:17  error  Capture group '([0-9a-f]+)' should be converted to a named or non-capturing group    prefer-named-capture-group

✖ 4 problems (4 errors, 0 warnings)

Rule no-useless-assignment

/modules/websocket/library_godot_websocket.js
  156:7  error  This assigned value is not used in subsequent statements  no-useless-assignment
  174:7  error  This assigned value is not used in subsequent statements  no-useless-assignment

/platform/web/js/engine/engine.js
  154:10  error  This assigned value is not used in subsequent statements  no-useless-assignment

/platform/web/js/libs/audio.worklet.js
  129:44  error  This assigned value is not used in subsequent statements  no-useless-assignment

✖ 4 problems (4 errors, 0 warnings)

Rule consistent-this

/platform/web/js/engine/engine.js
   88:11  error  Unexpected alias 'me' for 'this'  consistent-this
  148:11  error  Unexpected alias 'me' for 'this'  consistent-this
  209:11  error  Unexpected alias 'me' for 'this'  consistent-this

/platform/web/js/engine/preloader.js
  111:10  error  Unexpected alias 'me' for 'this'  consistent-this

✖ 4 problems (4 errors, 0 warnings)

Rule new-cap

/misc/dist/html/editor.html
  695:5  error  A function with a name starting with an uppercase letter should only be used as a constructor  new-cap

/platform/web/js/engine/engine.js
  96:8  error  A function with a name starting with an uppercase letter should only be used as a constructor  new-cap

/platform/web/js/libs/library_godot_runtime.js
  89:11  error  A function with a name starting with an uppercase letter should only be used as a constructor  new-cap

✖ 3 problems (3 errors, 0 warnings)

Rule prefer-promise-reject-errors

/platform/web/js/libs/library_godot_input.js
  324:7  error  Expected the Promise rejection reason to be an Error  prefer-promise-reject-errors
  329:6  error  Expected the Promise rejection reason to be an Error  prefer-promise-reject-errors

✖ 2 problems (2 errors, 0 warnings)

Rule no-warning-comments

/platform/web/js/libs/library_godot_os.js
  344:3  error  Unexpected 'todo' comment: 'TODO Godot core needs fixing to avoid...'  no-warning-comments

/platform/web/js/libs/library_godot_runtime.js
  94:62  error  Unexpected 'todo' comment: 'TODO wasm64'  no-warning-comments

✖ 2 problems (2 errors, 0 warnings)

Rule no-empty-function

/modules/webxr/native/library_godot_webxr.js
  365:19  error  Unexpected empty arrow function  no-empty-function

/platform/web/js/libs/library_godot_os.js
  236:29  error  Unexpected empty method 'request_quit'  no-empty-function

✖ 2 problems (2 errors, 0 warnings)

Rule func-name-matching

/misc/dist/html/editor.html
  676:3  error  Function name `progressFunction` should match property name `onProgress`  func-name-matching

✖ 1 problem (1 error, 0 warnings)

Rule complexity

/modules/webxr/native/library_godot_webxr.js
  485:35  error  Method 'godot_webxr_update_input_source' has a complexity of 26. Maximum allowed is 20  complexity

✖ 1 problem (1 error, 0 warnings)

@patwork
Copy link
Contributor

patwork commented May 11, 2024

After further inspection:

Rule no-eq-null

❓ Thoughts? Is it too strict?

Rule eqeqeq

❓ Thoughts? Is it too strict?

Rule init-declarations

⁉️ Propably can be turned off, looks like it sometimes conflicts with no-useless-assignment.

Rule require-unicode-regexp

❌ Off.

Rule prefer-named-capture-group

❌ Off.

Rule no-useless-assignment

⁉️ Propably can be turned off, see init-declarations.

Rule consistent-this

❓ Thoughts? Looks like it forces the use of the variable "that" when assigning "this".

/eslint consistent-this: ["error", "that"]/

Rule new-cap

❌ Off. Conflicts with "Godot", "UTF8ToString", etc.

Rule prefer-promise-reject-errors

❓ Thoughts?

add_file: function (entry) {
GodotInputDragDrop.promises.push(new Promise(function (resolve, reject) {
entry.file(function (file) {
const reader = new FileReader();
reader.onload = function () {
const f = {
'path': file.relativePath || file.webkitRelativePath,
'name': file.name,
'type': file.type,
'size': file.size,
'data': reader.result,
};
if (!f['path']) {
f['path'] = f['name'];
}
GodotInputDragDrop.pending_files.push(f);
resolve();
};
reader.onerror = function () {
GodotRuntime.print('Error reading file');
reject();
};
reader.readAsArrayBuffer(file);
}, function (err) {
GodotRuntime.print('Error!');
reject();
});
}));
},

Rule no-warning-comments

⁉️ Thoughts? These are user reminders, but will changing the level of this rule from "error" to "warn" result in failing github tasks?

Rule no-empty-function

❌ Off.

Rule func-name-matching

❓ Thoughts?

'onProgress': function progressFunction(current, total) {

I don't think this function needs a name at all?

Rule complexity

❌ Off.

@patwork
Copy link
Contributor

patwork commented May 12, 2024

@Faless I have set up a separate PR #91863

@Faless Faless closed this May 12, 2024
@AThousandShips AThousandShips removed this from the 4.x milestone May 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants