Browse Source

Merge pull request #43747 from Faless/js/4.0_lint

[HTML5] Linting, fixes.
Rémi Verschelde 4 years ago
parent
commit
50db0e66ac

+ 7 - 1
.github/workflows/static_checks.yml

@@ -9,7 +9,7 @@ jobs:
       - name: Checkout
         uses: actions/checkout@v2
 
-      # Azure repositories are not reliable, we need to prevent azure giving us packages.
+      # Azure repositories are not reliable, we need to prevent Azure giving us packages.
       - name: Make apt sources.list use the default Ubuntu repositories
         run: |
           sudo rm -f /etc/apt/sources.list.d/*
@@ -33,6 +33,12 @@ jobs:
         run: |
           bash ./misc/scripts/black_format.sh
 
+      - name: JavaScript style checks via ESLint
+        run: |
+          cd platform/javascript
+          npm ci
+          npm run lint
+
       - name: Documentation checks
         run: |
           doc/tools/makerst.py --dry-run doc/classes modules

+ 128 - 120
modules/webrtc/library_godot_webrtc.js

@@ -28,11 +28,11 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 
-var GodotRTCDataChannel = {
+const GodotRTCDataChannel = {
 	// Our socket implementation that forwards events to C++.
-	$GodotRTCDataChannel__deps: ['$IDHandler', '$GodotOS'],
+	$GodotRTCDataChannel__deps: ['$IDHandler', '$GodotRuntime'],
 	$GodotRTCDataChannel: {
-		connect: function(p_id, p_on_open, p_on_message, p_on_error, p_on_close) {
+		connect: function (p_id, p_on_open, p_on_message, p_on_error, p_on_close) {
 			const ref = IDHandler.get(p_id);
 			if (!ref) {
 				return;
@@ -48,31 +48,31 @@ var GodotRTCDataChannel = {
 			ref.onerror = function (event) {
 				p_on_error();
 			};
-			ref.onmessage = function(event) {
-				var buffer;
-				var is_string = 0;
+			ref.onmessage = function (event) {
+				let buffer;
+				let is_string = 0;
 				if (event.data instanceof ArrayBuffer) {
 					buffer = new Uint8Array(event.data);
 				} else if (event.data instanceof Blob) {
-					console.error("Blob type not supported");
+					GodotRuntime.error('Blob type not supported');
 					return;
-				} else if (typeof event.data === "string") {
+				} else if (typeof event.data === 'string') {
 					is_string = 1;
-					var enc = new TextEncoder("utf-8");
+					const enc = new TextEncoder('utf-8');
 					buffer = new Uint8Array(enc.encode(event.data));
 				} else {
-					console.error("Unknown message type");
+					GodotRuntime.error('Unknown message type');
 					return;
 				}
-				var len = buffer.length*buffer.BYTES_PER_ELEMENT;
-				var out = _malloc(len);
+				const len = buffer.length * buffer.BYTES_PER_ELEMENT;
+				const out = GodotRuntime.malloc(len);
 				HEAPU8.set(buffer, out);
 				p_on_message(out, len, is_string);
-				_free(out);
-			}
+				GodotRuntime.free(out);
+			};
 		},
 
-		close: function(p_id) {
+		close: function (p_id) {
 			const ref = IDHandler.get(p_id);
 			if (!ref) {
 				return;
@@ -84,40 +84,40 @@ var GodotRTCDataChannel = {
 			ref.close();
 		},
 
-		get_prop: function(p_id, p_prop, p_def) {
+		get_prop: function (p_id, p_prop, p_def) {
 			const ref = IDHandler.get(p_id);
 			return (ref && ref[p_prop] !== undefined) ? ref[p_prop] : p_def;
 		},
 	},
 
-	godot_js_rtc_datachannel_ready_state_get: function(p_id) {
+	godot_js_rtc_datachannel_ready_state_get: function (p_id) {
 		const ref = IDHandler.get(p_id);
 		if (!ref) {
 			return 3; // CLOSED
 		}
 
-		switch(ref.readyState) {
-			case "connecting":
-				return 0;
-			case "open":
-				return 1;
-			case "closing":
-				return 2;
-			case "closed":
-				return 3;
+		switch (ref.readyState) {
+		case 'connecting':
+			return 0;
+		case 'open':
+			return 1;
+		case 'closing':
+			return 2;
+		case 'closed':
+		default:
+			return 3;
 		}
-		return 3; // CLOSED
 	},
 
-	godot_js_rtc_datachannel_send: function(p_id, p_buffer, p_length, p_raw) {
+	godot_js_rtc_datachannel_send: function (p_id, p_buffer, p_length, p_raw) {
 		const ref = IDHandler.get(p_id);
 		if (!ref) {
 			return 1;
 		}
 
 		const bytes_array = new Uint8Array(p_length);
-		for (var i = 0; i < p_length; i++) {
-			bytes_array[i] = getValue(p_buffer + i, 'i8');
+		for (let i = 0; i < p_length; i++) {
+			bytes_array[i] = GodotRuntime.getHeapValue(p_buffer + i, 'i8');
 		}
 
 		if (p_raw) {
@@ -126,17 +126,18 @@ var GodotRTCDataChannel = {
 			const string = new TextDecoder('utf-8').decode(bytes_array);
 			ref.send(string);
 		}
+		return 0;
 	},
 
-	godot_js_rtc_datachannel_is_ordered: function(p_id) {
+	godot_js_rtc_datachannel_is_ordered: function (p_id) {
 		return IDHandler.get_prop(p_id, 'ordered', true);
 	},
 
-	godot_js_rtc_datachannel_id_get: function(p_id) {
+	godot_js_rtc_datachannel_id_get: function (p_id) {
 		return IDHandler.get_prop(p_id, 'id', 65535);
 	},
 
-	godot_js_rtc_datachannel_max_packet_lifetime_get: function(p_id) {
+	godot_js_rtc_datachannel_max_packet_lifetime_get: function (p_id) {
 		const ref = IDHandler.get(p_id);
 		if (!ref) {
 			return 65535;
@@ -150,44 +151,44 @@ var GodotRTCDataChannel = {
 		return 65535;
 	},
 
-	godot_js_rtc_datachannel_max_retransmits_get: function(p_id) {
+	godot_js_rtc_datachannel_max_retransmits_get: function (p_id) {
 		return IDHandler.get_prop(p_id, 'maxRetransmits', 65535);
 	},
 
-	godot_js_rtc_datachannel_is_negotiated: function(p_id, p_def) {
+	godot_js_rtc_datachannel_is_negotiated: function (p_id, p_def) {
 		return IDHandler.get_prop(p_id, 'negotiated', 65535);
 	},
 
-	godot_js_rtc_datachannel_label_get: function(p_id) {
+	godot_js_rtc_datachannel_label_get: function (p_id) {
 		const ref = IDHandler.get(p_id);
 		if (!ref || !ref.label) {
 			return 0;
 		}
-		return GodotOS.allocString(ref.label);
+		return GodotRuntime.allocString(ref.label);
 	},
 
-	godot_js_rtc_datachannel_protocol_get: function(p_id) {
+	godot_js_rtc_datachannel_protocol_get: function (p_id) {
 		const ref = IDHandler.get(p_id);
 		if (!ref || !ref.protocol) {
 			return 0;
 		}
-		return GodotOS.allocString(ref.protocol);
+		return GodotRuntime.allocString(ref.protocol);
 	},
 
-	godot_js_rtc_datachannel_destroy: function(p_id) {
+	godot_js_rtc_datachannel_destroy: function (p_id) {
 		GodotRTCDataChannel.close(p_id);
 		IDHandler.remove(p_id);
 	},
 
-	godot_js_rtc_datachannel_connect: function(p_id, p_ref, p_on_open, p_on_message, p_on_error, p_on_close) {
-		const onopen = GodotOS.get_func(p_on_open).bind(null, p_ref);
-		const onmessage = GodotOS.get_func(p_on_message).bind(null, p_ref);
-		const onerror = GodotOS.get_func(p_on_error).bind(null, p_ref);
-		const onclose = GodotOS.get_func(p_on_close).bind(null, p_ref);
+	godot_js_rtc_datachannel_connect: function (p_id, p_ref, p_on_open, p_on_message, p_on_error, p_on_close) {
+		const onopen = GodotRuntime.get_func(p_on_open).bind(null, p_ref);
+		const onmessage = GodotRuntime.get_func(p_on_message).bind(null, p_ref);
+		const onerror = GodotRuntime.get_func(p_on_error).bind(null, p_ref);
+		const onclose = GodotRuntime.get_func(p_on_close).bind(null, p_ref);
 		GodotRTCDataChannel.connect(p_id, onopen, onmessage, onerror, onclose);
 	},
 
-	godot_js_rtc_datachannel_close: function(p_id) {
+	godot_js_rtc_datachannel_close: function (p_id) {
 		const ref = IDHandler.get(p_id);
 		if (!ref) {
 			return;
@@ -199,48 +200,55 @@ var GodotRTCDataChannel = {
 autoAddDeps(GodotRTCDataChannel, '$GodotRTCDataChannel');
 mergeInto(LibraryManager.library, GodotRTCDataChannel);
 
-var GodotRTCPeerConnection = {
-	$GodotRTCPeerConnection__deps: ['$IDHandler', '$GodotOS', '$GodotRTCDataChannel'],
+const GodotRTCPeerConnection = {
+	$GodotRTCPeerConnection__deps: ['$IDHandler', '$GodotRuntime', '$GodotRTCDataChannel'],
 	$GodotRTCPeerConnection: {
-		onstatechange: function(p_id, p_conn, callback, event) {
+		onstatechange: function (p_id, p_conn, callback, event) {
 			const ref = IDHandler.get(p_id);
 			if (!ref) {
 				return;
 			}
-			var state = 5; // CLOSED
-			switch(p_conn.iceConnectionState) {
-				case "new":
-					state = 0;
-				case "checking":
-					state = 1;
-				case "connected":
-				case "completed":
-					state = 2;
-				case "disconnected":
-					state = 3;
-				case "failed":
-					state = 4;
-				case "closed":
-					state = 5;
+			let state = 5; // CLOSED
+			switch (p_conn.iceConnectionState) {
+			case 'new':
+				state = 0;
+				break;
+			case 'checking':
+				state = 1;
+				break;
+			case 'connected':
+			case 'completed':
+				state = 2;
+				break;
+			case 'disconnected':
+				state = 3;
+				break;
+			case 'failed':
+				state = 4;
+				break;
+			case 'closed':
+			default:
+				state = 5;
+				break;
 			}
 			callback(state);
 		},
 
-		onicecandidate: function(p_id, callback, event) {
+		onicecandidate: function (p_id, callback, event) {
 			const ref = IDHandler.get(p_id);
 			if (!ref || !event.candidate) {
 				return;
 			}
 
-			let c = event.candidate;
-			let candidate_str = GodotOS.allocString(c.candidate);
-			let mid_str = GodotOS.allocString(c.sdpMid);
+			const c = event.candidate;
+			const candidate_str = GodotRuntime.allocString(c.candidate);
+			const mid_str = GodotRuntime.allocString(c.sdpMid);
 			callback(mid_str, c.sdpMLineIndex, candidate_str);
-			_free(candidate_str);
-			_free(mid_str);
+			GodotRuntime.free(candidate_str);
+			GodotRuntime.free(mid_str);
 		},
 
-		ondatachannel: function(p_id, callback, event) {
+		ondatachannel: function (p_id, callback, event) {
 			const ref = IDHandler.get(p_id);
 			if (!ref) {
 				return;
@@ -250,39 +258,39 @@ var GodotRTCPeerConnection = {
 			callback(cid);
 		},
 
-		onsession: function(p_id, callback, session) {
+		onsession: function (p_id, callback, session) {
 			const ref = IDHandler.get(p_id);
 			if (!ref) {
 				return;
 			}
-			let type_str = GodotOS.allocString(session.type);
-			let sdp_str = GodotOS.allocString(session.sdp);
+			const type_str = GodotRuntime.allocString(session.type);
+			const sdp_str = GodotRuntime.allocString(session.sdp);
 			callback(type_str, sdp_str);
-			_free(type_str);
-			_free(sdp_str);
+			GodotRuntime.free(type_str);
+			GodotRuntime.free(sdp_str);
 		},
 
-		onerror: function(p_id, callback, error) {
+		onerror: function (p_id, callback, error) {
 			const ref = IDHandler.get(p_id);
 			if (!ref) {
 				return;
 			}
-			console.error(error);
+			GodotRuntime.error(error);
 			callback();
 		},
 	},
 
-	godot_js_rtc_pc_create: function(p_config, p_ref, p_on_state_change, p_on_candidate, p_on_datachannel) {
-		const onstatechange = GodotOS.get_func(p_on_state_change).bind(null, p_ref);
-		const oncandidate = GodotOS.get_func(p_on_candidate).bind(null, p_ref);
-		const ondatachannel = GodotOS.get_func(p_on_datachannel).bind(null, p_ref);
+	godot_js_rtc_pc_create: function (p_config, p_ref, p_on_state_change, p_on_candidate, p_on_datachannel) {
+		const onstatechange = GodotRuntime.get_func(p_on_state_change).bind(null, p_ref);
+		const oncandidate = GodotRuntime.get_func(p_on_candidate).bind(null, p_ref);
+		const ondatachannel = GodotRuntime.get_func(p_on_datachannel).bind(null, p_ref);
 
-		var config = JSON.parse(UTF8ToString(p_config));
-		var conn = null;
+		const config = JSON.parse(GodotRuntime.parseString(p_config));
+		let conn = null;
 		try {
 			conn = new RTCPeerConnection(config);
 		} catch (e) {
-			console.error(e);
+			GodotRuntime.error(e);
 			return 0;
 		}
 
@@ -294,7 +302,7 @@ var GodotRTCPeerConnection = {
 		return id;
 	},
 
-	godot_js_rtc_pc_close: function(p_id) {
+	godot_js_rtc_pc_close: function (p_id) {
 		const ref = IDHandler.get(p_id);
 		if (!ref) {
 			return;
@@ -302,7 +310,7 @@ var GodotRTCPeerConnection = {
 		ref.close();
 	},
 
-	godot_js_rtc_pc_destroy: function(p_id) {
+	godot_js_rtc_pc_destroy: function (p_id) {
 		const ref = IDHandler.get(p_id);
 		if (!ref) {
 			return;
@@ -313,93 +321,93 @@ var GodotRTCPeerConnection = {
 		IDHandler.remove(p_id);
 	},
 
-	godot_js_rtc_pc_offer_create: function(p_id, p_obj, p_on_session, p_on_error) {
+	godot_js_rtc_pc_offer_create: function (p_id, p_obj, p_on_session, p_on_error) {
 		const ref = IDHandler.get(p_id);
 		if (!ref) {
 			return;
 		}
-		const onsession = GodotOS.get_func(p_on_session).bind(null, p_obj);
-		const onerror = GodotOS.get_func(p_on_error).bind(null, p_obj);
-		ref.createOffer().then(function(session) {
+		const onsession = GodotRuntime.get_func(p_on_session).bind(null, p_obj);
+		const onerror = GodotRuntime.get_func(p_on_error).bind(null, p_obj);
+		ref.createOffer().then(function (session) {
 			GodotRTCPeerConnection.onsession(p_id, onsession, session);
-		}).catch(function(error) {
+		}).catch(function (error) {
 			GodotRTCPeerConnection.onerror(p_id, onerror, error);
 		});
 	},
 
-	godot_js_rtc_pc_local_description_set: function(p_id, p_type, p_sdp, p_obj, p_on_error) {
+	godot_js_rtc_pc_local_description_set: function (p_id, p_type, p_sdp, p_obj, p_on_error) {
 		const ref = IDHandler.get(p_id);
 		if (!ref) {
 			return;
 		}
-		const type = UTF8ToString(p_type);
-		const sdp = UTF8ToString(p_sdp);
-		const onerror = GodotOS.get_func(p_on_error).bind(null, p_obj);
+		const type = GodotRuntime.parseString(p_type);
+		const sdp = GodotRuntime.parseString(p_sdp);
+		const onerror = GodotRuntime.get_func(p_on_error).bind(null, p_obj);
 		ref.setLocalDescription({
 			'sdp': sdp,
-			'type': type
-		}).catch(function(error) {
+			'type': type,
+		}).catch(function (error) {
 			GodotRTCPeerConnection.onerror(p_id, onerror, error);
 		});
 	},
 
-	godot_js_rtc_pc_remote_description_set: function(p_id, p_type, p_sdp, p_obj, p_session_created, p_on_error) {
+	godot_js_rtc_pc_remote_description_set: function (p_id, p_type, p_sdp, p_obj, p_session_created, p_on_error) {
 		const ref = IDHandler.get(p_id);
 		if (!ref) {
 			return;
 		}
-		const type = UTF8ToString(p_type);
-		const sdp = UTF8ToString(p_sdp);
-		const onerror = GodotOS.get_func(p_on_error).bind(null, p_obj);
-		const onsession = GodotOS.get_func(p_session_created).bind(null, p_obj);
+		const type = GodotRuntime.parseString(p_type);
+		const sdp = GodotRuntime.parseString(p_sdp);
+		const onerror = GodotRuntime.get_func(p_on_error).bind(null, p_obj);
+		const onsession = GodotRuntime.get_func(p_session_created).bind(null, p_obj);
 		ref.setRemoteDescription({
 			'sdp': sdp,
-			'type': type
-		}).then(function() {
-			if (type != 'offer') {
-				return;
+			'type': type,
+		}).then(function () {
+			if (type !== 'offer') {
+				return Promise.resolve();
 			}
-			return ref.createAnswer().then(function(session) {
+			return ref.createAnswer().then(function (session) {
 				GodotRTCPeerConnection.onsession(p_id, onsession, session);
 			});
-		}).catch(function(error) {
+		}).catch(function (error) {
 			GodotRTCPeerConnection.onerror(p_id, onerror, error);
 		});
 	},
 
-	godot_js_rtc_pc_ice_candidate_add: function(p_id, p_mid_name, p_mline_idx, p_sdp) {
+	godot_js_rtc_pc_ice_candidate_add: function (p_id, p_mid_name, p_mline_idx, p_sdp) {
 		const ref = IDHandler.get(p_id);
 		if (!ref) {
 			return;
 		}
-		var sdpMidName = UTF8ToString(p_mid_name);
-		var sdpName = UTF8ToString(p_sdp);
+		const sdpMidName = GodotRuntime.parseString(p_mid_name);
+		const sdpName = GodotRuntime.parseString(p_sdp);
 		ref.addIceCandidate(new RTCIceCandidate({
-			"candidate": sdpName,
-			"sdpMid": sdpMidName,
-			"sdpMlineIndex": p_mline_idx,
+			'candidate': sdpName,
+			'sdpMid': sdpMidName,
+			'sdpMlineIndex': p_mline_idx,
 		}));
 	},
 
 	godot_js_rtc_pc_datachannel_create__deps: ['$GodotRTCDataChannel'],
-	godot_js_rtc_pc_datachannel_create: function(p_id, p_label, p_config) {
+	godot_js_rtc_pc_datachannel_create: function (p_id, p_label, p_config) {
 		try {
 			const ref = IDHandler.get(p_id);
 			if (!ref) {
 				return 0;
 			}
 
-			const label = UTF8ToString(p_label);
-			const config = JSON.parse(UTF8ToString(p_config));
+			const label = GodotRuntime.parseString(p_label);
+			const config = JSON.parse(GodotRuntime.parseString(p_config));
 
 			const channel = ref.createDataChannel(label, config);
 			return IDHandler.add(channel);
 		} catch (e) {
-			console.error(e);
+			GodotRuntime.error(e);
 			return 0;
 		}
 	},
 };
 
-autoAddDeps(GodotRTCPeerConnection, '$GodotRTCPeerConnection')
+autoAddDeps(GodotRTCPeerConnection, '$GodotRTCPeerConnection');
 mergeInto(LibraryManager.library, GodotRTCPeerConnection);

+ 46 - 46
modules/websocket/library_godot_websocket.js

@@ -28,51 +28,51 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 
-var GodotWebSocket = {
+const GodotWebSocket = {
 	// Our socket implementation that forwards events to C++.
-	$GodotWebSocket__deps: ['$IDHandler'],
+	$GodotWebSocket__deps: ['$IDHandler', '$GodotRuntime'],
 	$GodotWebSocket: {
 		// Connection opened, report selected protocol
-		_onopen: function(p_id, callback, event) {
+		_onopen: function (p_id, callback, event) {
 			const ref = IDHandler.get(p_id);
 			if (!ref) {
 				return; // Godot object is gone.
 			}
-			let c_str = GodotOS.allocString(ref.protocol);
+			const c_str = GodotRuntime.allocString(ref.protocol);
 			callback(c_str);
-			_free(c_str);
+			GodotRuntime.free(c_str);
 		},
 
 		// Message received, report content and type (UTF8 vs binary)
-		_onmessage: function(p_id, callback, event) {
+		_onmessage: function (p_id, callback, event) {
 			const ref = IDHandler.get(p_id);
 			if (!ref) {
 				return; // Godot object is gone.
 			}
-			var buffer;
-			var is_string = 0;
+			let buffer;
+			let is_string = 0;
 			if (event.data instanceof ArrayBuffer) {
 				buffer = new Uint8Array(event.data);
 			} else if (event.data instanceof Blob) {
-				alert("Blob type not supported");
+				GodotRuntime.error('Blob type not supported');
 				return;
-			} else if (typeof event.data === "string") {
+			} else if (typeof event.data === 'string') {
 				is_string = 1;
-				var enc = new TextEncoder("utf-8");
+				const enc = new TextEncoder('utf-8');
 				buffer = new Uint8Array(enc.encode(event.data));
 			} else {
-				alert("Unknown message type");
+				GodotRuntime.error('Unknown message type');
 				return;
 			}
-			var len = buffer.length*buffer.BYTES_PER_ELEMENT;
-			var out = _malloc(len);
+			const len = buffer.length * buffer.BYTES_PER_ELEMENT;
+			const out = GodotRuntime.malloc(len);
 			HEAPU8.set(buffer, out);
 			callback(out, len, is_string);
-			_free(out);
+			GodotRuntime.free(out);
 		},
 
 		// An error happened, 'onclose' will be called after this.
-		_onerror: function(p_id, callback, event) {
+		_onerror: function (p_id, callback, event) {
 			const ref = IDHandler.get(p_id);
 			if (!ref) {
 				return; // Godot object is gone.
@@ -81,27 +81,27 @@ var GodotWebSocket = {
 		},
 
 		// Connection is closed, this is always fired. Report close code, reason, and clean status.
-		_onclose: function(p_id, callback, event) {
+		_onclose: function (p_id, callback, event) {
 			const ref = IDHandler.get(p_id);
 			if (!ref) {
 				return; // Godot object is gone.
 			}
-			let c_str = GodotOS.allocString(event.reason);
+			const c_str = GodotRuntime.allocString(event.reason);
 			callback(event.code, c_str, event.wasClean ? 1 : 0);
-			_free(c_str);
+			GodotRuntime.free(c_str);
 		},
 
 		// Send a message
-		send: function(p_id, p_data) {
+		send: function (p_id, p_data) {
 			const ref = IDHandler.get(p_id);
-			if (!ref || ref.readyState != ref.OPEN) {
+			if (!ref || ref.readyState !== ref.OPEN) {
 				return 1; // Godot object is gone or socket is not in a ready state.
 			}
 			ref.send(p_data);
 			return 0;
 		},
 
-		create: function(socket, p_on_open, p_on_message, p_on_error, p_on_close) {
+		create: function (socket, p_on_open, p_on_message, p_on_error, p_on_close) {
 			const id = IDHandler.add(socket);
 			socket.onopen = GodotWebSocket._onopen.bind(null, id, p_on_open);
 			socket.onmessage = GodotWebSocket._onmessage.bind(null, id, p_on_message);
@@ -111,17 +111,17 @@ var GodotWebSocket = {
 		},
 
 		// Closes the JavaScript WebSocket (if not already closing) associated to a given C++ object.
-		close: function(p_id, p_code, p_reason) {
+		close: function (p_id, p_code, p_reason) {
 			const ref = IDHandler.get(p_id);
 			if (ref && ref.readyState < ref.CLOSING) {
 				const code = p_code;
-				const reason = UTF8ToString(p_reason);
+				const reason = GodotRuntime.parseString(p_reason);
 				ref.close(code, reason);
 			}
 		},
 
 		// Deletes the reference to a C++ object (closing any connected socket if necessary).
-		destroy: function(p_id) {
+		destroy: function (p_id) {
 			const ref = IDHandler.get(p_id);
 			if (!ref) {
 				return;
@@ -135,50 +135,50 @@ var GodotWebSocket = {
 		},
 	},
 
-	godot_js_websocket_create: function(p_ref, p_url, p_proto, p_on_open, p_on_message, p_on_error, p_on_close) {
-		const on_open = GodotOS.get_func(p_on_open).bind(null, p_ref);
-		const on_message = GodotOS.get_func(p_on_message).bind(null, p_ref);
-		const on_error = GodotOS.get_func(p_on_error).bind(null, p_ref);
-		const on_close = GodotOS.get_func(p_on_close).bind(null, p_ref);
-		const url = UTF8ToString(p_url);
-		const protos = UTF8ToString(p_proto);
-		var socket = null;
+	godot_js_websocket_create: function (p_ref, p_url, p_proto, p_on_open, p_on_message, p_on_error, p_on_close) {
+		const on_open = GodotRuntime.get_func(p_on_open).bind(null, p_ref);
+		const on_message = GodotRuntime.get_func(p_on_message).bind(null, p_ref);
+		const on_error = GodotRuntime.get_func(p_on_error).bind(null, p_ref);
+		const on_close = GodotRuntime.get_func(p_on_close).bind(null, p_ref);
+		const url = GodotRuntime.parseString(p_url);
+		const protos = GodotRuntime.parseString(p_proto);
+		let socket = null;
 		try {
 			if (protos) {
-				socket = new WebSocket(url, protos.split(","));
+				socket = new WebSocket(url, protos.split(','));
 			} else {
 				socket = new WebSocket(url);
 			}
 		} catch (e) {
 			return 0;
 		}
-		socket.binaryType = "arraybuffer";
+		socket.binaryType = 'arraybuffer';
 		return GodotWebSocket.create(socket, on_open, on_message, on_error, on_close);
 	},
 
-	godot_js_websocket_send: function(p_id, p_buf, p_buf_len, p_raw) {
-		var bytes_array = new Uint8Array(p_buf_len);
-		var i = 0;
-		for(i = 0; i < p_buf_len; i++) {
-			bytes_array[i] = getValue(p_buf + i, 'i8');
+	godot_js_websocket_send: function (p_id, p_buf, p_buf_len, p_raw) {
+		const bytes_array = new Uint8Array(p_buf_len);
+		let i = 0;
+		for (i = 0; i < p_buf_len; i++) {
+			bytes_array[i] = GodotRuntime.getHeapValue(p_buf + i, 'i8');
 		}
-		var out = bytes_array.buffer;
+		let out = bytes_array.buffer;
 		if (!p_raw) {
-			out = new TextDecoder("utf-8").decode(bytes_array);
+			out = new TextDecoder('utf-8').decode(bytes_array);
 		}
 		return GodotWebSocket.send(p_id, out);
 	},
 
-	godot_js_websocket_close: function(p_id, p_code, p_reason) {
+	godot_js_websocket_close: function (p_id, p_code, p_reason) {
 		const code = p_code;
-		const reason = UTF8ToString(p_reason);
+		const reason = GodotRuntime.parseString(p_reason);
 		GodotWebSocket.close(p_id, code, reason);
 	},
 
-	godot_js_websocket_destroy: function(p_id) {
+	godot_js_websocket_destroy: function (p_id) {
 		GodotWebSocket.destroy(p_id);
 	},
 };
 
-autoAddDeps(GodotWebSocket, '$GodotWebSocket')
+autoAddDeps(GodotWebSocket, '$GodotWebSocket');
 mergeInto(LibraryManager.library, GodotWebSocket);

+ 10 - 0
platform/javascript/.eslintrc.engine.js

@@ -0,0 +1,10 @@
+module.exports = {
+	"extends": [
+		"./.eslintrc.js",
+	],
+	"globals": {
+		"Godot": true,
+		"Preloader": true,
+		"Utils": true,
+	},
+};

+ 43 - 0
platform/javascript/.eslintrc.js

@@ -0,0 +1,43 @@
+module.exports = {
+	"env": {
+		"browser": true,
+		"es2021": true,
+	},
+	"extends": [
+		"airbnb-base",
+	],
+	"parserOptions": {
+		"ecmaVersion": 12,
+	},
+	"ignorePatterns": "*.externs.js",
+	"rules": {
+		"func-names": "off",
+		// Use tabs for consistency with the C++ codebase.
+		"indent": ["error", "tab"],
+		"max-len": "off",
+		"no-else-return": ["error", {allowElseIf: true}],
+		"curly": ["error", "all"],
+		"brace-style": ["error", "1tbs", { "allowSingleLine": false }],
+		"no-bitwise": "off",
+		"no-continue": "off",
+		"no-self-assign": "off",
+		"no-tabs": "off",
+		"no-param-reassign": ["error", { "props": false }],
+		"no-plusplus": "off",
+		"no-unused-vars": ["error", { "args": "none" }],
+		"prefer-destructuring": "off",
+		"prefer-rest-params": "off",
+		"prefer-spread": "off",
+		"camelcase": "off",
+		"no-underscore-dangle": "off",
+		"max-classes-per-file": "off",
+		"prefer-arrow-callback": "off",
+		// Messes up with copyright headers in source files.
+		"spaced-comment": "off",
+		// Completely breaks emscripten libraries.
+		"object-shorthand": "off",
+		// Closure compiler (exported properties)
+		"quote-props": ["error", "consistent"],
+		"dot-notation": "off",
+	}
+};

+ 22 - 0
platform/javascript/.eslintrc.libs.js

@@ -0,0 +1,22 @@
+module.exports = {
+	"extends": [
+		"./.eslintrc.js",
+	],
+	"globals": {
+		"LibraryManager": true,
+		"mergeInto": true,
+		"autoAddDeps": true,
+		"HEAP8": true,
+		"HEAPU8": true,
+		"HEAP32": true,
+		"HEAPF32": true,
+		"ERRNO_CODES": true,
+		"FS": true,
+		"IDBFS": true,
+		"GodotOS": true,
+		"GodotConfig": true,
+		"GodotRuntime": true,
+		"GodotFS": true,
+		"IDHandler": true,
+	},
+};

+ 12 - 11
platform/javascript/SCsub

@@ -20,27 +20,28 @@ build = env.add_program(build_targets, javascript_files)
 
 env.AddJSLibraries(
     [
-        "native/http_request.js",
-        "native/library_godot_audio.js",
-        "native/library_godot_display.js",
-        "native/library_godot_os.js",
+        "js/libs/library_godot_audio.js",
+        "js/libs/library_godot_display.js",
+        "js/libs/library_godot_http_request.js",
+        "js/libs/library_godot_os.js",
+        "js/libs/library_godot_runtime.js",
     ]
 )
 
 if env["tools"]:
-    env.AddJSLibraries(["native/library_godot_editor_tools.js"])
+    env.AddJSLibraries(["js/libs/library_godot_editor_tools.js"])
 if env["javascript_eval"]:
-    env.AddJSLibraries(["native/library_godot_eval.js"])
+    env.AddJSLibraries(["js/libs/library_godot_eval.js"])
 for lib in env["JS_LIBS"]:
     env.Append(LINKFLAGS=["--js-library", lib])
 env.Depends(build, env["JS_LIBS"])
 
 engine = [
-    "engine/preloader.js",
-    "engine/utils.js",
-    "engine/engine.js",
+    "js/engine/preloader.js",
+    "js/engine/utils.js",
+    "js/engine/engine.js",
 ]
-externs = [env.File("#platform/javascript/engine/externs.js")]
+externs = [env.File("#platform/javascript/js/engine/engine.externs.js")]
 js_engine = env.CreateEngineFile("#bin/godot${PROGSUFFIX}.engine.js", engine, externs)
 env.Depends(js_engine, externs)
 
@@ -59,7 +60,7 @@ out_files = [
     zip_dir.File(binary_name + ".audio.worklet.js"),
 ]
 html_file = "#misc/dist/html/editor.html" if env["tools"] else "#misc/dist/html/full-size.html"
-in_files = [js_wrapped, build[1], html_file, "#platform/javascript/native/audio.worklet.js"]
+in_files = [js_wrapped, build[1], html_file, "#platform/javascript/js/libs/audio.worklet.js"]
 if env["threads_enabled"]:
     in_files.append(build[2])
     out_files.append(zip_dir.File(binary_name + ".worker.js"))

+ 0 - 135
platform/javascript/engine/preloader.js

@@ -1,135 +0,0 @@
-var Preloader = /** @constructor */ function() {
-	var DOWNLOAD_ATTEMPTS_MAX = 4;
-	var progressFunc = null;
-	var lastProgress = { loaded: 0, total: 0 };
-
-	var loadingFiles = {};
-	this.preloadedFiles = [];
-
-	function loadXHR(resolve, reject, file, tracker) {
-		var xhr = new XMLHttpRequest;
-		xhr.open('GET', file);
-		if (!file.endsWith('.js')) {
-			xhr.responseType = 'arraybuffer';
-		}
-		['loadstart', 'progress', 'load', 'error', 'abort'].forEach(function(ev) {
-			xhr.addEventListener(ev, onXHREvent.bind(xhr, resolve, reject, file, tracker));
-		});
-		xhr.send();
-	}
-
-	function onXHREvent(resolve, reject, file, tracker, ev) {
-		if (this.status >= 400) {
-			if (this.status < 500 || ++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) {
-				reject(new Error("Failed loading file '" + file + "': " + this.statusText));
-				this.abort();
-				return;
-			} else {
-				setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000);
-			}
-		}
-
-		switch (ev.type) {
-			case 'loadstart':
-				if (tracker[file] === undefined) {
-					tracker[file] = {
-						total: ev.total,
-						loaded: ev.loaded,
-						attempts: 0,
-						final: false,
-					};
-				}
-				break;
-
-			case 'progress':
-				tracker[file].loaded = ev.loaded;
-				tracker[file].total = ev.total;
-				break;
-
-			case 'load':
-				tracker[file].final = true;
-				resolve(this);
-				break;
-
-			case 'error':
-				if (++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) {
-					tracker[file].final = true;
-					reject(new Error("Failed loading file '" + file + "'"));
-				} else {
-					setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000);
-				}
-				break;
-
-			case 'abort':
-				tracker[file].final = true;
-				reject(new Error("Loading file '" + file + "' was aborted."));
-				break;
-		}
-	}
-
-	this.loadPromise = function(file) {
-		return new Promise(function(resolve, reject) {
-			loadXHR(resolve, reject, file, loadingFiles);
-		});
-	}
-
-	this.preload = function(pathOrBuffer, destPath) {
-		if (pathOrBuffer instanceof ArrayBuffer) {
-			pathOrBuffer = new Uint8Array(pathOrBuffer);
-		} else if (ArrayBuffer.isView(pathOrBuffer)) {
-			pathOrBuffer = new Uint8Array(pathOrBuffer.buffer);
-		}
-		if (pathOrBuffer instanceof Uint8Array) {
-			this.preloadedFiles.push({
-				path: destPath,
-				buffer: pathOrBuffer
-			});
-			return Promise.resolve();
-		} else if (typeof pathOrBuffer === 'string') {
-			var me = this;
-			return this.loadPromise(pathOrBuffer).then(function(xhr) {
-				me.preloadedFiles.push({
-					path: destPath || pathOrBuffer,
-					buffer: xhr.response
-				});
-				return Promise.resolve();
-			});
-		} else {
-			throw Promise.reject("Invalid object for preloading");
-		}
-	};
-
-	var animateProgress = function() {
-		var loaded = 0;
-		var total = 0;
-		var totalIsValid = true;
-		var progressIsFinal = true;
-
-		Object.keys(loadingFiles).forEach(function(file) {
-			const stat = loadingFiles[file];
-			if (!stat.final) {
-				progressIsFinal = false;
-			}
-			if (!totalIsValid || stat.total === 0) {
-				totalIsValid = false;
-				total = 0;
-			} else {
-				total += stat.total;
-			}
-			loaded += stat.loaded;
-		});
-		if (loaded !== lastProgress.loaded || total !== lastProgress.total) {
-			lastProgress.loaded = loaded;
-			lastProgress.total = total;
-			if (typeof progressFunc === 'function')
-				progressFunc(loaded, total);
-		}
-		if (!progressIsFinal)
-			requestAnimationFrame(animateProgress);
-	}
-	this.animateProgress = animateProgress; // Also exposed to start it.
-
-	this.setProgressFunc = function(callback) {
-		progressFunc = callback;
-	}
-};

+ 0 - 0
platform/javascript/engine/externs.js → platform/javascript/js/engine/engine.externs.js


+ 82 - 68
platform/javascript/engine/engine.js → platform/javascript/js/engine/engine.js

@@ -1,14 +1,14 @@
-Function('return this')()['Engine'] = (function() {
-	var preloader = new Preloader();
+const Engine = (function () {
+	const preloader = new Preloader();
 
-	var wasmExt = '.wasm';
-	var unloadAfterInit = true;
-	var loadPath = '';
-	var loadPromise = null;
-	var initPromise = null;
-	var stderr = null;
-	var stdout = null;
-	var progressFunc = null;
+	let wasmExt = '.wasm';
+	let unloadAfterInit = true;
+	let loadPath = '';
+	let loadPromise = null;
+	let initPromise = null;
+	let stderr = null;
+	let stdout = null;
+	let progressFunc = null;
 
 	function load(basePath) {
 		if (loadPromise == null) {
@@ -18,14 +18,14 @@ Function('return this')()['Engine'] = (function() {
 			requestAnimationFrame(preloader.animateProgress);
 		}
 		return loadPromise;
-	};
+	}
 
 	function unload() {
 		loadPromise = null;
-	};
+	}
 
 	/** @constructor */
-	function Engine() {
+	function Engine() { // eslint-disable-line no-shadow
 		this.canvas = null;
 		this.executableName = '';
 		this.rtenv = null;
@@ -34,30 +34,32 @@ Function('return this')()['Engine'] = (function() {
 		this.onExecute = null;
 		this.onExit = null;
 		this.persistentPaths = ['/userfs'];
-	};
+	}
 
-	Engine.prototype.init = /** @param {string=} basePath */ function(basePath) {
+	Engine.prototype.init = /** @param {string=} basePath */ function (basePath) {
 		if (initPromise) {
 			return initPromise;
 		}
 		if (loadPromise == null) {
 			if (!basePath) {
-				initPromise = Promise.reject(new Error("A base path must be provided when calling `init` and the engine is not loaded."));
+				initPromise = Promise.reject(new Error('A base path must be provided when calling `init` and the engine is not loaded.'));
 				return initPromise;
 			}
 			load(basePath);
 		}
-		var config = {};
-		if (typeof stdout === 'function')
+		let config = {};
+		if (typeof stdout === 'function') {
 			config.print = stdout;
-		if (typeof stderr === 'function')
+		}
+		if (typeof stderr === 'function') {
 			config.printErr = stderr;
-		var me = this;
-		initPromise = new Promise(function(resolve, reject) {
+		}
+		const me = this;
+		initPromise = new Promise(function (resolve, reject) {
 			config['locateFile'] = Utils.createLocateRewrite(loadPath);
 			config['instantiateWasm'] = Utils.createInstantiatePromise(loadPromise);
-			Godot(config).then(function(module) {
-				module['initFS'](me.persistentPaths).then(function(fs_err) {
+			Godot(config).then(function (module) {
+				module['initFS'](me.persistentPaths).then(function (fs_err) {
 					me.rtenv = module;
 					if (unloadAfterInit) {
 						unload();
@@ -71,25 +73,28 @@ Function('return this')()['Engine'] = (function() {
 	};
 
 	/** @type {function(string, string):Object} */
-	Engine.prototype.preloadFile = function(file, path) {
+	Engine.prototype.preloadFile = function (file, path) {
 		return preloader.preload(file, path);
 	};
 
 	/** @type {function(...string):Object} */
-	Engine.prototype.start = function() {
+	Engine.prototype.start = function () {
 		// Start from arguments.
-		var args = [];
-		for (var i = 0; i < arguments.length; i++) {
+		const args = [];
+		for (let i = 0; i < arguments.length; i++) {
 			args.push(arguments[i]);
 		}
-		var me = this;
-		return me.init().then(function() {
+		const me = this;
+		return me.init().then(function () {
 			if (!me.rtenv) {
 				return Promise.reject(new Error('The engine must be initialized before it can be started'));
 			}
 
 			if (!(me.canvas instanceof HTMLCanvasElement)) {
 				me.canvas = Utils.findCanvas();
+				if (!me.canvas) {
+					return Promise.reject(new Error('No canvas found in page'));
+				}
 			}
 
 			// Canvas can grab focus on click, or key events won't work.
@@ -98,18 +103,18 @@ Function('return this')()['Engine'] = (function() {
 			}
 
 			// Disable right-click context menu.
-			me.canvas.addEventListener('contextmenu', function(ev) {
+			me.canvas.addEventListener('contextmenu', function (ev) {
 				ev.preventDefault();
 			}, false);
 
 			// Until context restoration is implemented warn the user of context loss.
-			me.canvas.addEventListener('webglcontextlost', function(ev) {
-				alert("WebGL context lost, please reload the page");
+			me.canvas.addEventListener('webglcontextlost', function (ev) {
+				alert('WebGL context lost, please reload the page'); // eslint-disable-line no-alert
 				ev.preventDefault();
 			}, false);
 
 			// Browser locale, or custom one if defined.
-			var locale = me.customLocale;
+			let locale = me.customLocale;
 			if (!locale) {
 				locale = navigator.languages ? navigator.languages[0] : navigator.language;
 				locale = locale.split('.')[0];
@@ -122,14 +127,14 @@ Function('return this')()['Engine'] = (function() {
 				'resizeCanvasOnStart': me.resizeCanvasOnStart,
 				'canvas': me.canvas,
 				'locale': locale,
-				'onExecute': function(p_args) {
+				'onExecute': function (p_args) {
 					if (me.onExecute) {
 						me.onExecute(p_args);
 						return 0;
 					}
 					return 1;
 				},
-				'onExit': function(p_code) {
+				'onExit': function (p_code) {
 					me.rtenv['deinitFS']();
 					if (me.onExit) {
 						me.onExit(p_code);
@@ -138,8 +143,8 @@ Function('return this')()['Engine'] = (function() {
 				},
 			});
 
-			return new Promise(function(resolve, reject) {
-				preloader.preloadedFiles.forEach(function(file) {
+			return new Promise(function (resolve, reject) {
+				preloader.preloadedFiles.forEach(function (file) {
 					me.rtenv['copyToFS'](file.path, file.buffer);
 				});
 				preloader.preloadedFiles.length = 0; // Clear memory
@@ -150,95 +155,101 @@ Function('return this')()['Engine'] = (function() {
 		});
 	};
 
-	Engine.prototype.startGame = function(execName, mainPack, extraArgs) {
+	Engine.prototype.startGame = function (execName, mainPack, extraArgs) {
 		// Start and init with execName as loadPath if not inited.
 		this.executableName = execName;
-		var me = this;
+		const me = this;
 		return Promise.all([
 			this.init(execName),
-			this.preloadFile(mainPack, mainPack)
-		]).then(function() {
-			var args = ['--main-pack', mainPack];
-			if (extraArgs)
+			this.preloadFile(mainPack, mainPack),
+		]).then(function () {
+			let args = ['--main-pack', mainPack];
+			if (extraArgs) {
 				args = args.concat(extraArgs);
+			}
 			return me.start.apply(me, args);
 		});
 	};
 
-	Engine.prototype.setWebAssemblyFilenameExtension = function(override) {
+	Engine.prototype.setWebAssemblyFilenameExtension = function (override) {
 		if (String(override).length === 0) {
 			throw new Error('Invalid WebAssembly filename extension override');
 		}
 		wasmExt = String(override);
 	};
 
-	Engine.prototype.setUnloadAfterInit = function(enabled) {
+	Engine.prototype.setUnloadAfterInit = function (enabled) {
 		unloadAfterInit = enabled;
 	};
 
-	Engine.prototype.setCanvas = function(canvasElem) {
+	Engine.prototype.setCanvas = function (canvasElem) {
 		this.canvas = canvasElem;
 	};
 
-	Engine.prototype.setCanvasResizedOnStart = function(enabled) {
+	Engine.prototype.setCanvasResizedOnStart = function (enabled) {
 		this.resizeCanvasOnStart = enabled;
 	};
 
-	Engine.prototype.setLocale = function(locale) {
+	Engine.prototype.setLocale = function (locale) {
 		this.customLocale = locale;
 	};
 
-	Engine.prototype.setExecutableName = function(newName) {
+	Engine.prototype.setExecutableName = function (newName) {
 		this.executableName = newName;
 	};
 
-	Engine.prototype.setProgressFunc = function(func) {
+	Engine.prototype.setProgressFunc = function (func) {
 		progressFunc = func;
 	};
 
-	Engine.prototype.setStdoutFunc = function(func) {
-		var print = function(text) {
+	Engine.prototype.setStdoutFunc = function (func) {
+		const print = function (text) {
+			let msg = text;
 			if (arguments.length > 1) {
-				text = Array.prototype.slice.call(arguments).join(" ");
+				msg = Array.prototype.slice.call(arguments).join(' ');
 			}
-			func(text);
+			func(msg);
 		};
-		if (this.rtenv)
+		if (this.rtenv) {
 			this.rtenv.print = print;
+		}
 		stdout = print;
 	};
 
-	Engine.prototype.setStderrFunc = function(func) {
-		var printErr = function(text) {
-			if (arguments.length > 1)
-				text = Array.prototype.slice.call(arguments).join(" ");
-			func(text);
+	Engine.prototype.setStderrFunc = function (func) {
+		const printErr = function (text) {
+			let msg = text;
+			if (arguments.length > 1) {
+				msg = Array.prototype.slice.call(arguments).join(' ');
+			}
+			func(msg);
 		};
-		if (this.rtenv)
+		if (this.rtenv) {
 			this.rtenv.printErr = printErr;
+		}
 		stderr = printErr;
 	};
 
-	Engine.prototype.setOnExecute = function(onExecute) {
+	Engine.prototype.setOnExecute = function (onExecute) {
 		this.onExecute = onExecute;
 	};
 
-	Engine.prototype.setOnExit = function(onExit) {
+	Engine.prototype.setOnExit = function (onExit) {
 		this.onExit = onExit;
 	};
 
-	Engine.prototype.copyToFS = function(path, buffer) {
+	Engine.prototype.copyToFS = function (path, buffer) {
 		if (this.rtenv == null) {
-			throw new Error("Engine must be inited before copying files");
+			throw new Error('Engine must be inited before copying files');
 		}
 		this.rtenv['copyToFS'](path, buffer);
 	};
 
-	Engine.prototype.setPersistentPaths = function(persistentPaths) {
+	Engine.prototype.setPersistentPaths = function (persistentPaths) {
 		this.persistentPaths = persistentPaths;
 	};
 
-	Engine.prototype.requestQuit = function() {
+	Engine.prototype.requestQuit = function () {
 		if (this.rtenv) {
 			this.rtenv['request_quit']();
 		}
@@ -268,4 +279,7 @@ Function('return this')()['Engine'] = (function() {
 	Engine.prototype['setPersistentPaths'] = Engine.prototype.setPersistentPaths;
 	Engine.prototype['requestQuit'] = Engine.prototype.requestQuit;
 	return Engine;
-})();
+}());
+if (typeof window !== 'undefined') {
+	window['Engine'] = Engine;
+}

+ 127 - 0
platform/javascript/js/engine/preloader.js

@@ -0,0 +1,127 @@
+const Preloader = /** @constructor */ function () { // eslint-disable-line no-unused-vars
+	const loadXHR = function (resolve, reject, file, tracker, attempts) {
+		const xhr = new XMLHttpRequest();
+		tracker[file] = {
+			total: 0,
+			loaded: 0,
+			final: false,
+		};
+		xhr.onerror = function () {
+			if (attempts <= 1) {
+				reject(new Error(`Failed loading file '${file}'`));
+			} else {
+				setTimeout(function () {
+					loadXHR(resolve, reject, file, tracker, attempts - 1);
+				}, 1000);
+			}
+		};
+		xhr.onabort = function () {
+			tracker[file].final = true;
+			reject(new Error(`Loading file '${file}' was aborted.`));
+		};
+		xhr.onloadstart = function (ev) {
+			tracker[file].total = ev.total;
+			tracker[file].loaded = ev.loaded;
+		};
+		xhr.onprogress = function (ev) {
+			tracker[file].loaded = ev.loaded;
+			tracker[file].total = ev.total;
+		};
+		xhr.onload = function () {
+			if (xhr.status >= 400) {
+				if (xhr.status < 500 || attempts <= 1) {
+					reject(new Error(`Failed loading file '${file}': ${xhr.statusText}`));
+					xhr.abort();
+				} else {
+					setTimeout(function () {
+						loadXHR(resolve, reject, file, tracker, attempts - 1);
+					}, 1000);
+				}
+			} else {
+				tracker[file].final = true;
+				resolve(xhr);
+			}
+		};
+		// Make request.
+		xhr.open('GET', file);
+		if (!file.endsWith('.js')) {
+			xhr.responseType = 'arraybuffer';
+		}
+		xhr.send();
+	};
+
+	const DOWNLOAD_ATTEMPTS_MAX = 4;
+	const loadingFiles = {};
+	const lastProgress = { loaded: 0, total: 0 };
+	let progressFunc = null;
+
+	const animateProgress = function () {
+		let loaded = 0;
+		let total = 0;
+		let totalIsValid = true;
+		let progressIsFinal = true;
+
+		Object.keys(loadingFiles).forEach(function (file) {
+			const stat = loadingFiles[file];
+			if (!stat.final) {
+				progressIsFinal = false;
+			}
+			if (!totalIsValid || stat.total === 0) {
+				totalIsValid = false;
+				total = 0;
+			} else {
+				total += stat.total;
+			}
+			loaded += stat.loaded;
+		});
+		if (loaded !== lastProgress.loaded || total !== lastProgress.total) {
+			lastProgress.loaded = loaded;
+			lastProgress.total = total;
+			if (typeof progressFunc === 'function') {
+				progressFunc(loaded, total);
+			}
+		}
+		if (!progressIsFinal) {
+			requestAnimationFrame(animateProgress);
+		}
+	};
+
+	this.animateProgress = animateProgress;
+
+	this.setProgressFunc = function (callback) {
+		progressFunc = callback;
+	};
+
+	this.loadPromise = function (file) {
+		return new Promise(function (resolve, reject) {
+			loadXHR(resolve, reject, file, loadingFiles, DOWNLOAD_ATTEMPTS_MAX);
+		});
+	};
+
+	this.preloadedFiles = [];
+	this.preload = function (pathOrBuffer, destPath) {
+		let buffer = null;
+		if (typeof pathOrBuffer === 'string') {
+			const me = this;
+			return this.loadPromise(pathOrBuffer).then(function (xhr) {
+				me.preloadedFiles.push({
+					path: destPath || pathOrBuffer,
+					buffer: xhr.response,
+				});
+				return Promise.resolve();
+			});
+		} else if (pathOrBuffer instanceof ArrayBuffer) {
+			buffer = new Uint8Array(pathOrBuffer);
+		} else if (ArrayBuffer.isView(pathOrBuffer)) {
+			buffer = new Uint8Array(pathOrBuffer.buffer);
+		}
+		if (buffer) {
+			this.preloadedFiles.push({
+				path: destPath,
+				buffer: pathOrBuffer,
+			});
+			return Promise.resolve();
+		}
+		return Promise.reject(new Error('Invalid object for preloading'));
+	};
+};

+ 24 - 19
platform/javascript/engine/utils.js → platform/javascript/js/engine/utils.js

@@ -1,51 +1,56 @@
-var Utils = {
-	createLocateRewrite: function(execName) {
+const Utils = { // eslint-disable-line no-unused-vars
+
+	createLocateRewrite: function (execName) {
 		function rw(path) {
 			if (path.endsWith('.worker.js')) {
-				return execName + '.worker.js';
+				return `${execName}.worker.js`;
 			} else if (path.endsWith('.audio.worklet.js')) {
-				return execName + '.audio.worklet.js';
+				return `${execName}.audio.worklet.js`;
 			} else if (path.endsWith('.js')) {
-				return execName + '.js';
+				return `${execName}.js`;
 			} else if (path.endsWith('.wasm')) {
-				return execName + '.wasm';
+				return `${execName}.wasm`;
 			}
+			return path;
 		}
 		return rw;
 	},
 
-	createInstantiatePromise: function(wasmLoader) {
+	createInstantiatePromise: function (wasmLoader) {
+		let loader = wasmLoader;
 		function instantiateWasm(imports, onSuccess) {
-			wasmLoader.then(function(xhr) {
-				WebAssembly.instantiate(xhr.response, imports).then(function(result) {
+			loader.then(function (xhr) {
+				WebAssembly.instantiate(xhr.response, imports).then(function (result) {
 					onSuccess(result['instance'], result['module']);
 				});
 			});
-			wasmLoader = null;
+			loader = null;
 			return {};
-		};
+		}
 
 		return instantiateWasm;
 	},
 
-	findCanvas: function() {
-		var nodes = document.getElementsByTagName('canvas');
+	findCanvas: function () {
+		const nodes = document.getElementsByTagName('canvas');
 		if (nodes.length && nodes[0] instanceof HTMLCanvasElement) {
 			return nodes[0];
 		}
-		throw new Error("No canvas found");
+		return null;
 	},
 
-	isWebGLAvailable: function(majorVersion = 1) {
-		var testContext = false;
+	isWebGLAvailable: function (majorVersion = 1) {
+		let testContext = false;
 		try {
-			var testCanvas = document.createElement('canvas');
+			const testCanvas = document.createElement('canvas');
 			if (majorVersion === 1) {
 				testContext = testCanvas.getContext('webgl') || testCanvas.getContext('experimental-webgl');
 			} else if (majorVersion === 2) {
 				testContext = testCanvas.getContext('webgl2') || testCanvas.getContext('experimental-webgl2');
 			}
-		} catch (e) {}
+		} catch (e) {
+			// Not available
+		}
 		return !!testContext;
-	}
+	},
 };

+ 14 - 13
platform/javascript/native/audio.worklet.js → platform/javascript/js/libs/audio.worklet.js

@@ -27,6 +27,7 @@
 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
+
 class RingBuffer {
 	constructor(p_buffer, p_state) {
 		this.buffer = p_buffer;
@@ -104,7 +105,7 @@ class GodotProcessor extends AudioWorkletProcessor {
 	}
 
 	parse_message(p_cmd, p_data) {
-		if (p_cmd == "start" && p_data) {
+		if (p_cmd === 'start' && p_data) {
 			const state = p_data[0];
 			let idx = 0;
 			this.lock = state.subarray(idx, ++idx);
@@ -113,14 +114,14 @@ class GodotProcessor extends AudioWorkletProcessor {
 			const avail_out = state.subarray(idx, ++idx);
 			this.input = new RingBuffer(p_data[1], avail_in);
 			this.output = new RingBuffer(p_data[2], avail_out);
-		} else if (p_cmd == "stop") {
+		} else if (p_cmd === 'stop') {
 			this.runing = false;
 			this.output = null;
 			this.input = null;
 		}
 	}
 
-	array_has_data(arr) {
+	static array_has_data(arr) {
 		return arr.length && arr[0].length && arr[0][0].length;
 	}
 
@@ -131,39 +132,39 @@ class GodotProcessor extends AudioWorkletProcessor {
 		if (this.output === null) {
 			return true; // Not ready yet, keep processing.
 		}
-		const process_input = this.array_has_data(inputs);
+		const process_input = GodotProcessor.array_has_data(inputs);
 		if (process_input) {
 			const input = inputs[0];
 			const chunk = input[0].length * input.length;
-			if (this.input_buffer.length != chunk) {
+			if (this.input_buffer.length !== chunk) {
 				this.input_buffer = new Float32Array(chunk);
 			}
 			if (this.input.space_left() >= chunk) {
-				this.write_input(this.input_buffer, input);
+				GodotProcessor.write_input(this.input_buffer, input);
 				this.input.write(this.input_buffer);
 			} else {
-				this.port.postMessage("Input buffer is full! Skipping input frame.");
+				this.port.postMessage('Input buffer is full! Skipping input frame.');
 			}
 		}
-		const process_output = this.array_has_data(outputs);
+		const process_output = GodotProcessor.array_has_data(outputs);
 		if (process_output) {
 			const output = outputs[0];
 			const chunk = output[0].length * output.length;
-			if (this.output_buffer.length != chunk) {
+			if (this.output_buffer.length !== chunk) {
 				this.output_buffer = new Float32Array(chunk);
 			}
 			if (this.output.data_left() >= chunk) {
 				this.output.read(this.output_buffer);
-				this.write_output(output, this.output_buffer);
+				GodotProcessor.write_output(output, this.output_buffer);
 			} else {
-				this.port.postMessage("Output buffer has not enough frames! Skipping output frame.");
+				this.port.postMessage('Output buffer has not enough frames! Skipping output frame.');
 			}
 		}
 		this.process_notify();
 		return true;
 	}
 
-	write_output(dest, source) {
+	static write_output(dest, source) {
 		const channels = dest.length;
 		for (let ch = 0; ch < channels; ch++) {
 			for (let sample = 0; sample < dest[ch].length; sample++) {
@@ -172,7 +173,7 @@ class GodotProcessor extends AudioWorkletProcessor {
 		}
 	}
 
-	write_input(dest, source) {
+	static write_input(dest, source) {
 		const channels = source.length;
 		for (let ch = 0; ch < channels; ch++) {
 			for (let sample = 0; sample < source[ch].length; sample++) {

+ 80 - 74
platform/javascript/native/library_godot_audio.js → platform/javascript/js/libs/library_godot_audio.js

@@ -29,73 +29,79 @@
 /*************************************************************************/
 
 const GodotAudio = {
-	$GodotAudio__deps: ['$GodotOS'],
+	$GodotAudio__deps: ['$GodotRuntime', '$GodotOS'],
 	$GodotAudio: {
 		ctx: null,
 		input: null,
 		driver: null,
 		interval: 0,
 
-		init: function(mix_rate, latency, onstatechange, onlatencyupdate) {
+		init: function (mix_rate, latency, onstatechange, onlatencyupdate) {
 			const ctx = new (window.AudioContext || window.webkitAudioContext)({
 				sampleRate: mix_rate,
 				// latencyHint: latency / 1000 // Do not specify, leave 'interactive' for good performance.
 			});
 			GodotAudio.ctx = ctx;
-			onstatechange(ctx.state); // Immeditately notify state.
-			ctx.onstatechange = function() {
+			ctx.onstatechange = function () {
 				let state = 0;
 				switch (ctx.state) {
-					case 'suspended':
-						state = 0;
-						break;
-					case 'running':
-						state = 1;
-						break;
-					case 'closed':
-						state = 2;
-						break;
+				case 'suspended':
+					state = 0;
+					break;
+				case 'running':
+					state = 1;
+					break;
+				case 'closed':
+					state = 2;
+					break;
+
+					// no default
 				}
 				onstatechange(state);
-			}
+			};
+			ctx.onstatechange(); // Immeditately notify state.
 			// Update computed latency
-			GodotAudio.interval = setInterval(function() {
-				let latency = 0;
+			GodotAudio.interval = setInterval(function () {
+				let computed_latency = 0;
 				if (ctx.baseLatency) {
-					latency += GodotAudio.ctx.baseLatency;
+					computed_latency += GodotAudio.ctx.baseLatency;
 				}
 				if (ctx.outputLatency) {
-					latency += GodotAudio.ctx.outputLatency;
+					computed_latency += GodotAudio.ctx.outputLatency;
 				}
-				onlatencyupdate(latency);
+				onlatencyupdate(computed_latency);
 			}, 1000);
 			GodotOS.atexit(GodotAudio.close_async);
 			return ctx.destination.channelCount;
 		},
 
-		create_input: function(callback) {
+		create_input: function (callback) {
 			if (GodotAudio.input) {
 				return; // Already started.
 			}
 			function gotMediaInput(stream) {
 				GodotAudio.input = GodotAudio.ctx.createMediaStreamSource(stream);
-				callback(GodotAudio.input)
+				callback(GodotAudio.input);
 			}
 			if (navigator.mediaDevices.getUserMedia) {
 				navigator.mediaDevices.getUserMedia({
-					"audio": true
-				}).then(gotMediaInput, function(e) { out(e) });
+					'audio': true,
+				}).then(gotMediaInput, function (e) {
+					GodotRuntime.print(e);
+				});
 			} else {
 				if (!navigator.getUserMedia) {
 					navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
 				}
 				navigator.getUserMedia({
-					"audio": true
-				}, gotMediaInput, function(e) { out(e) });
+					'audio': true,
+				}, gotMediaInput, function (e) {
+					GodotRuntime.print(e);
+				});
 			}
 		},
 
-		close_async: function(resolve, reject) {
+		close_async: function (resolve, reject) {
 			const ctx = GodotAudio.ctx;
 			GodotAudio.ctx = null;
 			// Audio was not initialized.
@@ -118,14 +124,14 @@ const GodotAudio = {
 			if (GodotAudio.driver) {
 				closed = GodotAudio.driver.close();
 			}
-			closed.then(function() {
+			closed.then(function () {
 				return ctx.close();
-			}).then(function() {
+			}).then(function () {
 				ctx.onstatechange = null;
 				resolve();
-			}).catch(function(e) {
+			}).catch(function (e) {
 				ctx.onstatechange = null;
-				console.error("Error closing AudioContext", e);
+				GodotRuntime.error('Error closing AudioContext', e);
 				resolve();
 			});
 		},
@@ -139,30 +145,30 @@ const GodotAudio = {
 		return 1;
 	},
 
-	godot_audio_init: function(p_mix_rate, p_latency, p_state_change, p_latency_update) {
-		const statechange = GodotOS.get_func(p_state_change);
-		const latencyupdate = GodotOS.get_func(p_latency_update);
+	godot_audio_init: function (p_mix_rate, p_latency, p_state_change, p_latency_update) {
+		const statechange = GodotRuntime.get_func(p_state_change);
+		const latencyupdate = GodotRuntime.get_func(p_latency_update);
 		return GodotAudio.init(p_mix_rate, p_latency, statechange, latencyupdate);
 	},
 
-	godot_audio_resume: function() {
-		if (GodotAudio.ctx && GodotAudio.ctx.state != 'running') {
+	godot_audio_resume: function () {
+		if (GodotAudio.ctx && GodotAudio.ctx.state !== 'running') {
 			GodotAudio.ctx.resume();
 		}
 	},
 
 	godot_audio_capture_start__proxy: 'sync',
-	godot_audio_capture_start: function() {
+	godot_audio_capture_start: function () {
 		if (GodotAudio.input) {
 			return; // Already started.
 		}
-		GodotAudio.create_input(function(input) {
+		GodotAudio.create_input(function (input) {
 			input.connect(GodotAudio.driver.get_node());
 		});
 	},
 
 	godot_audio_capture_stop__proxy: 'sync',
-	godot_audio_capture_stop: function() {
+	godot_audio_capture_stop: function () {
 		if (GodotAudio.input) {
 			const tracks = GodotAudio.input['mediaStream']['getTracks']();
 			for (let i = 0; i < tracks.length; i++) {
@@ -174,54 +180,54 @@ const GodotAudio = {
 	},
 };
 
-autoAddDeps(GodotAudio, "$GodotAudio");
+autoAddDeps(GodotAudio, '$GodotAudio');
 mergeInto(LibraryManager.library, GodotAudio);
 
 /**
  * The AudioWorklet API driver, used when threads are available.
  */
 const GodotAudioWorklet = {
-	$GodotAudioWorklet__deps: ['$GodotAudio'],
+	$GodotAudioWorklet__deps: ['$GodotAudio', '$GodotConfig'],
 	$GodotAudioWorklet: {
 		promise: null,
 		worklet: null,
 
-		create: function(channels) {
-			const path = Module['locateFile']('godot.audio.worklet.js');
-			GodotAudioWorklet.promise = GodotAudio.ctx.audioWorklet.addModule(path).then(function() {
+		create: function (channels) {
+			const path = GodotConfig.locate_file('godot.audio.worklet.js');
+			GodotAudioWorklet.promise = GodotAudio.ctx.audioWorklet.addModule(path).then(function () {
 				GodotAudioWorklet.worklet = new AudioWorkletNode(
 					GodotAudio.ctx,
 					'godot-processor',
 					{
-						'outputChannelCount': [channels]
-					}
+						'outputChannelCount': [channels],
+					},
 				);
 				return Promise.resolve();
 			});
 			GodotAudio.driver = GodotAudioWorklet;
 		},
 
-		start: function(in_buf, out_buf, state) {
-			GodotAudioWorklet.promise.then(function() {
+		start: function (in_buf, out_buf, state) {
+			GodotAudioWorklet.promise.then(function () {
 				const node = GodotAudioWorklet.worklet;
 				node.connect(GodotAudio.ctx.destination);
 				node.port.postMessage({
 					'cmd': 'start',
 					'data': [state, in_buf, out_buf],
 				});
-				node.port.onmessage = function(event) {
-					console.error(event.data);
+				node.port.onmessage = function (event) {
+					GodotRuntime.error(event.data);
 				};
 			});
 		},
 
-		get_node: function() {
+		get_node: function () {
 			return GodotAudioWorklet.worklet;
 		},
 
-		close: function() {
-			return new Promise(function(resolve, reject) {
-				GodotAudioWorklet.promise.then(function() {
+		close: function () {
+			return new Promise(function (resolve, reject) {
+				GodotAudioWorklet.promise.then(function () {
 					GodotAudioWorklet.worklet.port.postMessage({
 						'cmd': 'stop',
 						'data': null,
@@ -235,32 +241,32 @@ const GodotAudioWorklet = {
 		},
 	},
 
-	godot_audio_worklet_create: function(channels) {
+	godot_audio_worklet_create: function (channels) {
 		GodotAudioWorklet.create(channels);
 	},
 
-	godot_audio_worklet_start: function(p_in_buf, p_in_size, p_out_buf, p_out_size, p_state) {
-		const out_buffer = GodotOS.heapSub(HEAPF32, p_out_buf, p_out_size);
-		const in_buffer = GodotOS.heapSub(HEAPF32, p_in_buf, p_in_size);
-		const state = GodotOS.heapSub(HEAP32, p_state, 4);
+	godot_audio_worklet_start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, p_state) {
+		const out_buffer = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size);
+		const in_buffer = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size);
+		const state = GodotRuntime.heapSub(HEAP32, p_state, 4);
 		GodotAudioWorklet.start(in_buffer, out_buffer, state);
 	},
 
-	godot_audio_worklet_state_wait: function(p_state, p_idx, p_expected, p_timeout) {
+	godot_audio_worklet_state_wait: function (p_state, p_idx, p_expected, p_timeout) {
 		Atomics.wait(HEAP32, (p_state >> 2) + p_idx, p_expected, p_timeout);
 		return Atomics.load(HEAP32, (p_state >> 2) + p_idx);
 	},
 
-	godot_audio_worklet_state_add: function(p_state, p_idx, p_value) {
+	godot_audio_worklet_state_add: function (p_state, p_idx, p_value) {
 		return Atomics.add(HEAP32, (p_state >> 2) + p_idx, p_value);
 	},
 
-	godot_audio_worklet_state_get: function(p_state, p_idx) {
+	godot_audio_worklet_state_get: function (p_state, p_idx) {
 		return Atomics.load(HEAP32, (p_state >> 2) + p_idx);
 	},
 };
 
-autoAddDeps(GodotAudioWorklet, "$GodotAudioWorklet");
+autoAddDeps(GodotAudioWorklet, '$GodotAudioWorklet');
 mergeInto(LibraryManager.library, GodotAudioWorklet);
 
 /*
@@ -271,16 +277,16 @@ const GodotAudioScript = {
 	$GodotAudioScript: {
 		script: null,
 
-		create: function(buffer_length, channel_count) {
+		create: function (buffer_length, channel_count) {
 			GodotAudioScript.script = GodotAudio.ctx.createScriptProcessor(buffer_length, 2, channel_count);
 			GodotAudio.driver = GodotAudioScript;
 			return GodotAudioScript.script.bufferSize;
 		},
 
-		start: function(p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess) {
-			GodotAudioScript.script.onaudioprocess = function(event) {
+		start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess) {
+			GodotAudioScript.script.onaudioprocess = function (event) {
 				// Read input
-				const inb = GodotOS.heapSub(HEAPF32, p_in_buf, p_in_size);
+				const inb = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size);
 				const input = event.inputBuffer;
 				if (GodotAudio.input) {
 					const inlen = input.getChannelData(0).length;
@@ -296,7 +302,7 @@ const GodotAudioScript = {
 				onprocess();
 
 				// Write the output.
-				const outb = GodotOS.heapSub(HEAPF32, p_out_buf, p_out_size);
+				const outb = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size);
 				const output = event.outputBuffer;
 				const channels = output.numberOfChannels;
 				for (let ch = 0; ch < channels; ch++) {
@@ -310,12 +316,12 @@ const GodotAudioScript = {
 			GodotAudioScript.script.connect(GodotAudio.ctx.destination);
 		},
 
-		get_node: function() {
+		get_node: function () {
 			return GodotAudioScript.script;
 		},
 
-		close: function() {
-			return new Promise(function(resolve, reject) {
+		close: function () {
+			return new Promise(function (resolve, reject) {
 				GodotAudioScript.script.disconnect();
 				GodotAudioScript.script.onaudioprocess = null;
 				GodotAudioScript.script = null;
@@ -324,15 +330,15 @@ const GodotAudioScript = {
 		},
 	},
 
-	godot_audio_script_create: function(buffer_length, channel_count) {
+	godot_audio_script_create: function (buffer_length, channel_count) {
 		return GodotAudioScript.create(buffer_length, channel_count);
 	},
 
-	godot_audio_script_start: function(p_in_buf, p_in_size, p_out_buf, p_out_size, p_cb) {
-		const onprocess = GodotOS.get_func(p_cb);
+	godot_audio_script_start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, p_cb) {
+		const onprocess = GodotRuntime.get_func(p_cb);
 		GodotAudioScript.start(p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess);
 	},
 };
 
-autoAddDeps(GodotAudioScript, "$GodotAudioScript");
+autoAddDeps(GodotAudioScript, '$GodotAudioScript');
 mergeInto(LibraryManager.library, GodotAudioScript);

+ 119 - 118
platform/javascript/native/library_godot_display.js → platform/javascript/js/libs/library_godot_display.js

@@ -33,32 +33,33 @@
  * Keeps track of registered event listeners so it can remove them on shutdown.
  */
 const GodotDisplayListeners = {
+	$GodotDisplayListeners__deps: ['$GodotOS'],
 	$GodotDisplayListeners__postset: 'GodotOS.atexit(function(resolve, reject) { GodotDisplayListeners.clear(); resolve(); });',
 	$GodotDisplayListeners: {
 		handlers: [],
 
-		has: function(target, event, method, capture) {
-			return GodotDisplayListeners.handlers.findIndex(function(e) {
-				return e.target === target && e.event === event && e.method === method && e.capture == capture;
+		has: function (target, event, method, capture) {
+			return GodotDisplayListeners.handlers.findIndex(function (e) {
+				return e.target === target && e.event === event && e.method === method && e.capture === capture;
 			}) !== -1;
 		},
 
-		add: function(target, event, method, capture) {
+		add: function (target, event, method, capture) {
 			if (GodotDisplayListeners.has(target, event, method, capture)) {
 				return;
 			}
-			function Handler(target, event, method, capture) {
-				this.target = target;
-				this.event = event;
-				this.method = method;
-				this.capture = capture;
-			};
+			function Handler(p_target, p_event, p_method, p_capture) {
+				this.target = p_target;
+				this.event = p_event;
+				this.method = p_method;
+				this.capture = p_capture;
+			}
 			GodotDisplayListeners.handlers.push(new Handler(target, event, method, capture));
 			target.addEventListener(event, method, capture);
 		},
 
-		clear: function() {
-			GodotDisplayListeners.handlers.forEach(function(h) {
+		clear: function () {
+			GodotDisplayListeners.handlers.forEach(function (h) {
 				h.target.removeEventListener(h.event, h.method, h.capture);
 			});
 			GodotDisplayListeners.handlers.length = 0;
@@ -83,20 +84,20 @@ const GodotDisplayDragDrop = {
 		promises: [],
 		pending_files: [],
 
-		add_entry: function(entry) {
+		add_entry: function (entry) {
 			if (entry.isDirectory) {
 				GodotDisplayDragDrop.add_dir(entry);
 			} else if (entry.isFile) {
 				GodotDisplayDragDrop.add_file(entry);
 			} else {
-				console.error("Unrecognized entry...", entry);
+				GodotRuntime.error('Unrecognized entry...', entry);
 			}
 		},
 
-		add_dir: function(entry) {
-			GodotDisplayDragDrop.promises.push(new Promise(function(resolve, reject) {
+		add_dir: function (entry) {
+			GodotDisplayDragDrop.promises.push(new Promise(function (resolve, reject) {
 				const reader = entry.createReader();
-				reader.readEntries(function(entries) {
+				reader.readEntries(function (entries) {
 					for (let i = 0; i < entries.length; i++) {
 						GodotDisplayDragDrop.add_entry(entries[i]);
 					}
@@ -105,58 +106,58 @@ const GodotDisplayDragDrop = {
 			}));
 		},
 
-		add_file: function(entry) {
-			GodotDisplayDragDrop.promises.push(new Promise(function(resolve, reject) {
-				entry.file(function(file) {
+		add_file: function (entry) {
+			GodotDisplayDragDrop.promises.push(new Promise(function (resolve, reject) {
+				entry.file(function (file) {
 					const reader = new FileReader();
-					reader.onload = function() {
+					reader.onload = function () {
 						const f = {
-							"path": file.relativePath || file.webkitRelativePath,
-							"name": file.name,
-							"type": file.type,
-							"size": file.size,
-							"data": reader.result
+							'path': file.relativePath || file.webkitRelativePath,
+							'name': file.name,
+							'type': file.type,
+							'size': file.size,
+							'data': reader.result,
 						};
 						if (!f['path']) {
 							f['path'] = f['name'];
 						}
 						GodotDisplayDragDrop.pending_files.push(f);
-						resolve()
+						resolve();
 					};
-					reader.onerror = function() {
-						console.log("Error reading file");
+					reader.onerror = function () {
+						GodotRuntime.print('Error reading file');
 						reject();
-					}
+					};
 					reader.readAsArrayBuffer(file);
-				}, function(err) {
-					console.log("Error!");
+				}, function (err) {
+					GodotRuntime.print('Error!');
 					reject();
 				});
 			}));
 		},
 
-		process: function(resolve, reject) {
-			if (GodotDisplayDragDrop.promises.length == 0) {
+		process: function (resolve, reject) {
+			if (GodotDisplayDragDrop.promises.length === 0) {
 				resolve();
 				return;
 			}
-			GodotDisplayDragDrop.promises.pop().then(function() {
-				setTimeout(function() {
+			GodotDisplayDragDrop.promises.pop().then(function () {
+				setTimeout(function () {
 					GodotDisplayDragDrop.process(resolve, reject);
 				}, 0);
 			});
 		},
 
-		_process_event: function(ev, callback) {
+		_process_event: function (ev, callback) {
 			ev.preventDefault();
 			if (ev.dataTransfer.items) {
 				// Use DataTransferItemList interface to access the file(s)
 				for (let i = 0; i < ev.dataTransfer.items.length; i++) {
 					const item = ev.dataTransfer.items[i];
 					let entry = null;
-					if ("getAsEntry" in item) {
+					if ('getAsEntry' in item) {
 						entry = item.getAsEntry();
-					} else if ("webkitGetAsEntry" in item) {
+					} else if ('webkitGetAsEntry' in item) {
 						entry = item.webkitGetAsEntry();
 					}
 					if (entry) {
@@ -164,25 +165,25 @@ const GodotDisplayDragDrop = {
 					}
 				}
 			} else {
-				console.error("File upload not supported");
+				GodotRuntime.error('File upload not supported');
 			}
-			new Promise(GodotDisplayDragDrop.process).then(function() {
-				const DROP = "/tmp/drop-" + parseInt(Math.random() * Math.pow(2, 31)) + "/";
+			new Promise(GodotDisplayDragDrop.process).then(function () {
+				const DROP = `/tmp/drop-${parseInt(Math.random() * (1 << 30), 10)}/`;
 				const drops = [];
 				const files = [];
 				FS.mkdir(DROP);
 				GodotDisplayDragDrop.pending_files.forEach((elem) => {
 					const path = elem['path'];
 					GodotFS.copy_to_fs(DROP + path, elem['data']);
-					let idx = path.indexOf("/");
-					if (idx == -1) {
+					let idx = path.indexOf('/');
+					if (idx === -1) {
 						// Root file
 						drops.push(DROP + path);
 					} else {
 						// Subdir
 						const sub = path.substr(0, idx);
-						idx = sub.indexOf("/");
-						if (idx < 0 && drops.indexOf(DROP + sub) == -1) {
+						idx = sub.indexOf('/');
+						if (idx < 0 && drops.indexOf(DROP + sub) === -1) {
 							drops.push(DROP + sub);
 						}
 					}
@@ -191,37 +192,38 @@ const GodotDisplayDragDrop = {
 				GodotDisplayDragDrop.promises = [];
 				GodotDisplayDragDrop.pending_files = [];
 				callback(drops);
-				const dirs = [DROP.substr(0, DROP.length -1)];
+				const dirs = [DROP.substr(0, DROP.length - 1)];
 				// Remove temporary files
 				files.forEach(function (file) {
 					FS.unlink(file);
-					let dir = file.replace(DROP, "");
-					let idx = dir.lastIndexOf("/");
+					let dir = file.replace(DROP, '');
+					let idx = dir.lastIndexOf('/');
 					while (idx > 0) {
 						dir = dir.substr(0, idx);
-						if (dirs.indexOf(DROP + dir) == -1) {
+						if (dirs.indexOf(DROP + dir) === -1) {
 							dirs.push(DROP + dir);
 						}
-						idx = dir.lastIndexOf("/");
+						idx = dir.lastIndexOf('/');
 					}
 				});
 				// Remove dirs.
-				dirs.sort(function(a, b) {
+				dirs.sort(function (a, b) {
 					const al = (a.match(/\//g) || []).length;
 					const bl = (b.match(/\//g) || []).length;
-					if (al > bl)
+					if (al > bl) {
 						return -1;
-					else if (al < bl)
+					} else if (al < bl) {
 						return 1;
+					}
 					return 0;
-				}).forEach(function(dir) {
+				}).forEach(function (dir) {
 					FS.rmdir(dir);
 				});
 			});
 		},
 
-		handler: function(callback) {
-			return function(ev) {
+		handler: function (callback) {
+			return function (ev) {
 				GodotDisplayDragDrop._process_event(ev, callback);
 			};
 		},
@@ -234,31 +236,31 @@ mergeInto(LibraryManager.library, GodotDisplayDragDrop);
  * Keeps track of cursor status and custom shapes.
  */
 const GodotDisplayCursor = {
+	$GodotDisplayCursor__deps: ['$GodotOS', '$GodotConfig'],
 	$GodotDisplayCursor__postset: 'GodotOS.atexit(function(resolve, reject) { GodotDisplayCursor.clear(); resolve(); });',
-	$GodotDisplayCursor__deps: ['$GodotConfig', '$GodotOS'],
 	$GodotDisplayCursor: {
 		shape: 'auto',
 		visible: true,
 		cursors: {},
-		set_style: function(style) {
+		set_style: function (style) {
 			GodotConfig.canvas.style.cursor = style;
 		},
-		set_shape: function(shape) {
+		set_shape: function (shape) {
 			GodotDisplayCursor.shape = shape;
 			let css = shape;
 			if (shape in GodotDisplayCursor.cursors) {
 				const c = GodotDisplayCursor.cursors[shape];
-				css = 'url("' + c.url + '") ' + c.x + ' ' + c.y + ', auto';
+				css = `url("${c.url}") ${c.x} ${c.y}, auto`;
 			}
 			if (GodotDisplayCursor.visible) {
 				GodotDisplayCursor.set_style(css);
 			}
 		},
-		clear: function() {
+		clear: function () {
 			GodotDisplayCursor.set_style('');
 			GodotDisplayCursor.shape = 'auto';
 			GodotDisplayCursor.visible = true;
-			Object.keys(GodotDisplayCursor.cursors).forEach(function(key) {
+			Object.keys(GodotDisplayCursor.cursors).forEach(function (key) {
 				URL.revokeObjectURL(GodotDisplayCursor.cursors[key]);
 				delete GodotDisplayCursor.cursors[key];
 			});
@@ -273,75 +275,74 @@ mergeInto(LibraryManager.library, GodotDisplayCursor);
  * Exposes all the functions needed by DisplayServer implementation.
  */
 const GodotDisplay = {
-	$GodotDisplay__deps: ['$GodotConfig', '$GodotOS', '$GodotDisplayCursor', '$GodotDisplayListeners', '$GodotDisplayDragDrop'],
+	$GodotDisplay__deps: ['$GodotConfig', '$GodotRuntime', '$GodotDisplayCursor', '$GodotDisplayListeners', '$GodotDisplayDragDrop'],
 	$GodotDisplay: {
 		window_icon: '',
 	},
 
-	godot_js_display_is_swap_ok_cancel: function() {
+	godot_js_display_is_swap_ok_cancel: function () {
 		const win = (['Windows', 'Win64', 'Win32', 'WinCE']);
-		const plat = navigator.platform || "";
+		const plat = navigator.platform || '';
 		if (win.indexOf(plat) !== -1) {
 			return 1;
 		}
 		return 0;
 	},
 
-	godot_js_display_alert: function(p_text) {
-		window.alert(UTF8ToString(p_text));
+	godot_js_display_alert: function (p_text) {
+		window.alert(GodotRuntime.parseString(p_text)); // eslint-disable-line no-alert
 	},
 
-	godot_js_display_pixel_ratio_get: function() {
+	godot_js_display_pixel_ratio_get: function () {
 		return window.devicePixelRatio || 1;
 	},
 
 	/*
 	 * Canvas
 	 */
-	godot_js_display_canvas_focus: function() {
+	godot_js_display_canvas_focus: function () {
 		GodotConfig.canvas.focus();
 	},
 
-	godot_js_display_canvas_is_focused: function() {
-		return document.activeElement == GodotConfig.canvas;
+	godot_js_display_canvas_is_focused: function () {
+		return document.activeElement === GodotConfig.canvas;
 	},
 
-	godot_js_display_canvas_bounding_rect_position_get: function(r_x, r_y) {
+	godot_js_display_canvas_bounding_rect_position_get: function (r_x, r_y) {
 		const brect = GodotConfig.canvas.getBoundingClientRect();
-		setValue(r_x, brect.x, 'i32');
-		setValue(r_y, brect.y, 'i32');
+		GodotRuntime.setHeapValue(r_x, brect.x, 'i32');
+		GodotRuntime.setHeapValue(r_y, brect.y, 'i32');
 	},
 
 	/*
 	 * Touchscreen
 	 */
-	godot_js_display_touchscreen_is_available: function() {
+	godot_js_display_touchscreen_is_available: function () {
 		return 'ontouchstart' in window;
 	},
 
 	/*
 	 * Clipboard
 	 */
-	godot_js_display_clipboard_set: function(p_text) {
-		const text = UTF8ToString(p_text);
+	godot_js_display_clipboard_set: function (p_text) {
+		const text = GodotRuntime.parseString(p_text);
 		if (!navigator.clipboard || !navigator.clipboard.writeText) {
 			return 1;
 		}
-		navigator.clipboard.writeText(text).catch(function(e) {
+		navigator.clipboard.writeText(text).catch(function (e) {
 			// Setting OS clipboard is only possible from an input callback.
-			console.error("Setting OS clipboard is only possible from an input callback for the HTML5 plafrom. Exception:", e);
+			GodotRuntime.error('Setting OS clipboard is only possible from an input callback for the HTML5 plafrom. Exception:', e);
 		});
 		return 0;
 	},
 
-	godot_js_display_clipboard_get_deps: ['$GodotOS'],
-	godot_js_display_clipboard_get: function(callback) {
-		const func = GodotOS.get_func(callback);
+	godot_js_display_clipboard_get: function (callback) {
+		const func = GodotRuntime.get_func(callback);
 		try {
 			navigator.clipboard.readText().then(function (result) {
-				const ptr = allocate(intArrayFromString(result), ALLOC_NORMAL);
+				const ptr = GodotRuntime.allocString(result);
 				func(ptr);
-				_free(ptr);
+				GodotRuntime.free(ptr);
 			}).catch(function (e) {
 				// Fail graciously.
 			});
@@ -353,19 +354,19 @@ const GodotDisplay = {
 	/*
 	 * Window
 	 */
-	godot_js_display_window_request_fullscreen: function() {
+	godot_js_display_window_request_fullscreen: function () {
 		const canvas = GodotConfig.canvas;
-		(canvas.requestFullscreen || canvas.msRequestFullscreen ||
-			canvas.mozRequestFullScreen || canvas.mozRequestFullscreen ||
-			canvas.webkitRequestFullscreen
+		(canvas.requestFullscreen || canvas.msRequestFullscreen
+			|| canvas.mozRequestFullScreen || canvas.mozRequestFullscreen
+			|| canvas.webkitRequestFullscreen
 		).call(canvas);
 	},
 
-	godot_js_display_window_title_set: function(p_data) {
-		document.title = UTF8ToString(p_data);
+	godot_js_display_window_title_set: function (p_data) {
+		document.title = GodotRuntime.parseString(p_data);
 	},
 
-	godot_js_display_window_icon_set: function(p_ptr, p_len) {
+	godot_js_display_window_icon_set: function (p_ptr, p_len) {
 		let link = document.getElementById('-gd-engine-icon');
 		if (link === null) {
 			link = document.createElement('link');
@@ -374,7 +375,7 @@ const GodotDisplay = {
 			document.head.appendChild(link);
 		}
 		const old_icon = GodotDisplay.window_icon;
-		const png = new Blob([GodotOS.heapCopy(HEAPU8, p_ptr, p_len)], { type: "image/png" });
+		const png = new Blob([GodotRuntime.heapCopy(HEAPU8, p_ptr, p_len)], { type: 'image/png' });
 		GodotDisplay.window_icon = URL.createObjectURL(png);
 		link.href = GodotDisplay.window_icon;
 		if (old_icon) {
@@ -385,9 +386,9 @@ const GodotDisplay = {
 	/*
 	 * Cursor
 	 */
-	godot_js_display_cursor_set_visible: function(p_visible) {
-		const visible = p_visible != 0;
-		if (visible == GodotDisplayCursor.visible) {
+	godot_js_display_cursor_set_visible: function (p_visible) {
+		const visible = p_visible !== 0;
+		if (visible === GodotDisplayCursor.visible) {
 			return;
 		}
 		GodotDisplayCursor.visible = visible;
@@ -398,19 +399,19 @@ const GodotDisplay = {
 		}
 	},
 
-	godot_js_display_cursor_is_hidden: function() {
+	godot_js_display_cursor_is_hidden: function () {
 		return !GodotDisplayCursor.visible;
 	},
 
-	godot_js_display_cursor_set_shape: function(p_string) {
-		GodotDisplayCursor.set_shape(UTF8ToString(p_string));
+	godot_js_display_cursor_set_shape: function (p_string) {
+		GodotDisplayCursor.set_shape(GodotRuntime.parseString(p_string));
 	},
 
-	godot_js_display_cursor_set_custom_shape: function(p_shape, p_ptr, p_len, p_hotspot_x, p_hotspot_y) {
-		const shape = UTF8ToString(p_shape);
+	godot_js_display_cursor_set_custom_shape: function (p_shape, p_ptr, p_len, p_hotspot_x, p_hotspot_y) {
+		const shape = GodotRuntime.parseString(p_shape);
 		const old_shape = GodotDisplayCursor.cursors[shape];
 		if (p_len > 0) {
-			const png = new Blob([GodotOS.heapCopy(HEAPU8, p_ptr, p_len)], { type: 'image/png' });
+			const png = new Blob([GodotRuntime.heapCopy(HEAPU8, p_ptr, p_len)], { type: 'image/png' });
 			const url = URL.createObjectURL(png);
 			GodotDisplayCursor.cursors[shape] = {
 				url: url,
@@ -420,7 +421,7 @@ const GodotDisplay = {
 		} else {
 			delete GodotDisplayCursor.cursors[shape];
 		}
-		if (shape == GodotDisplayCursor.shape) {
+		if (shape === GodotDisplayCursor.shape) {
 			GodotDisplayCursor.set_shape(GodotDisplayCursor.shape);
 		}
 		if (old_shape) {
@@ -431,41 +432,41 @@ const GodotDisplay = {
 	/*
 	 * Listeners
 	 */
-	godot_js_display_notification_cb: function(callback, p_enter, p_exit, p_in, p_out) {
+	godot_js_display_notification_cb: function (callback, p_enter, p_exit, p_in, p_out) {
 		const canvas = GodotConfig.canvas;
-		const func = GodotOS.get_func(callback);
+		const func = GodotRuntime.get_func(callback);
 		const notif = [p_enter, p_exit, p_in, p_out];
-		['mouseover', 'mouseleave', 'focus', 'blur'].forEach(function(evt_name, idx) {
-			GodotDisplayListeners.add(canvas, evt_name, function() {
+		['mouseover', 'mouseleave', 'focus', 'blur'].forEach(function (evt_name, idx) {
+			GodotDisplayListeners.add(canvas, evt_name, function () {
 				func.bind(null, notif[idx]);
 			}, true);
 		});
 	},
 
-	godot_js_display_paste_cb: function(callback) {
-		const func = GodotOS.get_func(callback);
-		GodotDisplayListeners.add(window, 'paste', function(evt) {
+	godot_js_display_paste_cb: function (callback) {
+		const func = GodotRuntime.get_func(callback);
+		GodotDisplayListeners.add(window, 'paste', function (evt) {
 			const text = evt.clipboardData.getData('text');
-			const ptr = allocate(intArrayFromString(text), ALLOC_NORMAL);
+			const ptr = GodotRuntime.allocString(text);
 			func(ptr);
-			_free(ptr);
+			GodotRuntime.free(ptr);
 		}, false);
 	},
 
-	godot_js_display_drop_files_cb: function(callback) {
-		const func = GodotOS.get_func(callback)
-		const dropFiles = function(files) {
+	godot_js_display_drop_files_cb: function (callback) {
+		const func = GodotRuntime.get_func(callback);
+		const dropFiles = function (files) {
 			const args = files || [];
 			if (!args.length) {
 				return;
 			}
 			const argc = args.length;
-			const argv = GodotOS.allocStringArray(args);
+			const argv = GodotRuntime.allocStringArray(args);
 			func(argv, argc);
-			GodotOS.freeStringArray(argv, argc);
+			GodotRuntime.freeStringArray(argv, argc);
 		};
 		const canvas = GodotConfig.canvas;
-		GodotDisplayListeners.add(canvas, 'dragover', function(ev) {
+		GodotDisplayListeners.add(canvas, 'dragover', function (ev) {
 			// Prevent default behavior (which would try to open the file(s))
 			ev.preventDefault();
 		}, false);

+ 4 - 4
platform/javascript/native/library_godot_editor_tools.js → platform/javascript/js/libs/library_godot_editor_tools.js

@@ -30,10 +30,10 @@
 
 const GodotEditorTools = {
 	godot_js_editor_download_file__deps: ['$FS'],
-	godot_js_editor_download_file: function(p_path, p_name, p_mime) {
-		const path = UTF8ToString(p_path);
-		const name = UTF8ToString(p_name);
-		const mime = UTF8ToString(p_mime);
+	godot_js_editor_download_file: function (p_path, p_name, p_mime) {
+		const path = GodotRuntime.parseString(p_path);
+		const name = GodotRuntime.parseString(p_name);
+		const mime = GodotRuntime.parseString(p_mime);
 		const size = FS.stat(path)['size'];
 		const buf = new Uint8Array(size);
 		const fd = FS.open(path, 'r');

+ 34 - 34
platform/javascript/native/library_godot_eval.js → platform/javascript/js/libs/library_godot_eval.js

@@ -29,57 +29,57 @@
 /*************************************************************************/
 
 const GodotEval = {
-	godot_js_eval__deps: ['$GodotOS'],
-	godot_js_eval: function(p_js, p_use_global_ctx, p_union_ptr, p_byte_arr, p_byte_arr_write, p_callback) {
-		const js_code = UTF8ToString(p_js);
+	godot_js_eval__deps: ['$GodotRuntime'],
+	godot_js_eval: function (p_js, p_use_global_ctx, p_union_ptr, p_byte_arr, p_byte_arr_write, p_callback) {
+		const js_code = GodotRuntime.parseString(p_js);
 		let eval_ret = null;
 		try {
 			if (p_use_global_ctx) {
 				// indirect eval call grants global execution context
-				const global_eval = eval;
+				const global_eval = eval; // eslint-disable-line no-eval
 				eval_ret = global_eval(js_code);
 			} else {
-				eval_ret = eval(js_code);
+				eval_ret = eval(js_code); // eslint-disable-line no-eval
 			}
 		} catch (e) {
-			err(e);
+			GodotRuntime.error(e);
 		}
 
 		switch (typeof eval_ret) {
-			case 'boolean':
-				setValue(p_union_ptr, eval_ret, 'i32');
-				return 1; // BOOL
+		case 'boolean':
+			GodotRuntime.setHeapValue(p_union_ptr, eval_ret, 'i32');
+			return 1; // BOOL
 
-			case 'number':
-				setValue(p_union_ptr, eval_ret, 'double');
-				return 3; // REAL
+		case 'number':
+			GodotRuntime.setHeapValue(p_union_ptr, eval_ret, 'double');
+			return 3; // REAL
 
-			case 'string':
-				let array_ptr = GodotOS.allocString(eval_ret);
-				setValue(p_union_ptr, array_ptr , '*');
-				return 4; // STRING
+		case 'string':
+			GodotRuntime.setHeapValue(p_union_ptr, GodotRuntime.allocString(eval_ret), '*');
+			return 4; // STRING
 
-			case 'object':
-				if (eval_ret === null) {
-					break;
-				}
-
-				if (ArrayBuffer.isView(eval_ret) && !(eval_ret instanceof Uint8Array)) {
-					eval_ret = new Uint8Array(eval_ret.buffer);
-				}
-				else if (eval_ret instanceof ArrayBuffer) {
-					eval_ret = new Uint8Array(eval_ret);
-				}
-				if (eval_ret instanceof Uint8Array) {
-					const func = GodotOS.get_func(p_callback);
-					const bytes_ptr = func(p_byte_arr, p_byte_arr_write,  eval_ret.length);
-					HEAPU8.set(eval_ret, bytes_ptr);
-					return 20; // POOL_BYTE_ARRAY
-				}
+		case 'object':
+			if (eval_ret === null) {
 				break;
+			}
+
+			if (ArrayBuffer.isView(eval_ret) && !(eval_ret instanceof Uint8Array)) {
+				eval_ret = new Uint8Array(eval_ret.buffer);
+			} else if (eval_ret instanceof ArrayBuffer) {
+				eval_ret = new Uint8Array(eval_ret);
+			}
+			if (eval_ret instanceof Uint8Array) {
+				const func = GodotRuntime.get_func(p_callback);
+				const bytes_ptr = func(p_byte_arr, p_byte_arr_write, eval_ret.length);
+				HEAPU8.set(eval_ret, bytes_ptr);
+				return 20; // POOL_BYTE_ARRAY
+			}
+			break;
+
+			// no default
 		}
 		return 0; // NIL
 	},
-}
+};
 
 mergeInto(LibraryManager.library, GodotEval);

+ 45 - 44
platform/javascript/native/http_request.js → platform/javascript/js/libs/library_godot_http_request.js

@@ -27,118 +27,119 @@
 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
-var GodotHTTPRequest = {
+
+const GodotHTTPRequest = {
+	$GodotHTTPRequest__deps: ['$GodotRuntime'],
 	$GodotHTTPRequest: {
 		requests: [],
 
-		getUnusedRequestId: function() {
-			var idMax = GodotHTTPRequest.requests.length;
-			for (var potentialId = 0; potentialId < idMax; ++potentialId) {
+		getUnusedRequestId: function () {
+			const idMax = GodotHTTPRequest.requests.length;
+			for (let potentialId = 0; potentialId < idMax; ++potentialId) {
 				if (GodotHTTPRequest.requests[potentialId] instanceof XMLHttpRequest) {
 					continue;
 				}
 				return potentialId;
 			}
-			GodotHTTPRequest.requests.push(null)
+			GodotHTTPRequest.requests.push(null);
 			return idMax;
 		},
 
-		setupRequest: function(xhr) {
+		setupRequest: function (xhr) {
 			xhr.responseType = 'arraybuffer';
 		},
 	},
 
-	godot_xhr_new: function() {
-		var newId = GodotHTTPRequest.getUnusedRequestId();
-		GodotHTTPRequest.requests[newId] = new XMLHttpRequest;
+	godot_xhr_new: function () {
+		const newId = GodotHTTPRequest.getUnusedRequestId();
+		GodotHTTPRequest.requests[newId] = new XMLHttpRequest();
 		GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[newId]);
 		return newId;
 	},
 
-	godot_xhr_reset: function(xhrId) {
-		GodotHTTPRequest.requests[xhrId] = new XMLHttpRequest;
+	godot_xhr_reset: function (xhrId) {
+		GodotHTTPRequest.requests[xhrId] = new XMLHttpRequest();
 		GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[xhrId]);
 	},
 
-	godot_xhr_free: function(xhrId) {
+	godot_xhr_free: function (xhrId) {
 		GodotHTTPRequest.requests[xhrId].abort();
 		GodotHTTPRequest.requests[xhrId] = null;
 	},
 
-	godot_xhr_open: function(xhrId, method, url, user, password) {
-		user = user > 0 ? UTF8ToString(user) : null;
-		password = password > 0 ? UTF8ToString(password) : null;
-		GodotHTTPRequest.requests[xhrId].open(UTF8ToString(method), UTF8ToString(url), true, user, password);
+	godot_xhr_open: function (xhrId, method, url, p_user, p_password) {
+		const user = p_user > 0 ? GodotRuntime.parseString(p_user) : null;
+		const password = p_password > 0 ? GodotRuntime.parseString(p_password) : null;
+		GodotHTTPRequest.requests[xhrId].open(GodotRuntime.parseString(method), GodotRuntime.parseString(url), true, user, password);
 	},
 
-	godot_xhr_set_request_header: function(xhrId, header, value) {
-		GodotHTTPRequest.requests[xhrId].setRequestHeader(UTF8ToString(header), UTF8ToString(value));
+	godot_xhr_set_request_header: function (xhrId, header, value) {
+		GodotHTTPRequest.requests[xhrId].setRequestHeader(GodotRuntime.parseString(header), GodotRuntime.parseString(value));
 	},
 
-	godot_xhr_send_null: function(xhrId) {
+	godot_xhr_send_null: function (xhrId) {
 		GodotHTTPRequest.requests[xhrId].send();
 	},
 
-	godot_xhr_send_string: function(xhrId, strPtr) {
+	godot_xhr_send_string: function (xhrId, strPtr) {
 		if (!strPtr) {
-			err("Failed to send string per XHR: null pointer");
+			GodotRuntime.error('Failed to send string per XHR: null pointer');
 			return;
 		}
-		GodotHTTPRequest.requests[xhrId].send(UTF8ToString(strPtr));
+		GodotHTTPRequest.requests[xhrId].send(GodotRuntime.parseString(strPtr));
 	},
 
-	godot_xhr_send_data: function(xhrId, ptr, len) {
+	godot_xhr_send_data: function (xhrId, ptr, len) {
 		if (!ptr) {
-			err("Failed to send data per XHR: null pointer");
+			GodotRuntime.error('Failed to send data per XHR: null pointer');
 			return;
 		}
 		if (len < 0) {
-			err("Failed to send data per XHR: buffer length less than 0");
+			GodotRuntime.error('Failed to send data per XHR: buffer length less than 0');
 			return;
 		}
 		GodotHTTPRequest.requests[xhrId].send(HEAPU8.subarray(ptr, ptr + len));
 	},
 
-	godot_xhr_abort: function(xhrId) {
+	godot_xhr_abort: function (xhrId) {
 		GodotHTTPRequest.requests[xhrId].abort();
 	},
 
-	godot_xhr_get_status: function(xhrId) {
+	godot_xhr_get_status: function (xhrId) {
 		return GodotHTTPRequest.requests[xhrId].status;
 	},
 
-	godot_xhr_get_ready_state: function(xhrId) {
+	godot_xhr_get_ready_state: function (xhrId) {
 		return GodotHTTPRequest.requests[xhrId].readyState;
 	},
 
-	godot_xhr_get_response_headers_length: function(xhrId) {
-		var headers = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders();
-		return headers === null ? 0 : lengthBytesUTF8(headers);
+	godot_xhr_get_response_headers_length: function (xhrId) {
+		const headers = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders();
+		return headers === null ? 0 : GodotRuntime.strlen(headers);
 	},
 
-	godot_xhr_get_response_headers: function(xhrId, dst, len) {
-		var str = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders();
-		if (str === null)
+	godot_xhr_get_response_headers: function (xhrId, dst, len) {
+		const str = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders();
+		if (str === null) {
 			return;
-		var buf = new Uint8Array(len + 1);
-		stringToUTF8Array(str, buf, 0, buf.length);
-		buf = buf.subarray(0, -1);
-		HEAPU8.set(buf, dst);
+		}
+		GodotRuntime.stringToHeap(str, dst, len);
 	},
 
-	godot_xhr_get_response_length: function(xhrId) {
-		var body = GodotHTTPRequest.requests[xhrId].response;
+	godot_xhr_get_response_length: function (xhrId) {
+		const body = GodotHTTPRequest.requests[xhrId].response;
 		return body === null ? 0 : body.byteLength;
 	},
 
-	godot_xhr_get_response: function(xhrId, dst, len) {
-		var buf = GodotHTTPRequest.requests[xhrId].response;
-		if (buf === null)
+	godot_xhr_get_response: function (xhrId, dst, len) {
+		let buf = GodotHTTPRequest.requests[xhrId].response;
+		if (buf === null) {
 			return;
+		}
 		buf = new Uint8Array(buf).subarray(0, len);
 		HEAPU8.set(buf, dst);
 	},
 };
 
-autoAddDeps(GodotHTTPRequest, "$GodotHTTPRequest");
+autoAddDeps(GodotHTTPRequest, '$GodotHTTPRequest');
 mergeInto(LibraryManager.library, GodotHTTPRequest);

+ 57 - 89
platform/javascript/native/library_godot_os.js → platform/javascript/js/libs/library_godot_os.js

@@ -33,52 +33,57 @@ const IDHandler = {
 		_last_id: 0,
 		_references: {},
 
-		get: function(p_id) {
+		get: function (p_id) {
 			return IDHandler._references[p_id];
 		},
 
-		add: function(p_data) {
+		add: function (p_data) {
 			const id = ++IDHandler._last_id;
 			IDHandler._references[id] = p_data;
 			return id;
 		},
 
-		remove: function(p_id) {
+		remove: function (p_id) {
 			delete IDHandler._references[p_id];
 		},
 	},
 };
 
-autoAddDeps(IDHandler, "$IDHandler");
+autoAddDeps(IDHandler, '$IDHandler');
 mergeInto(LibraryManager.library, IDHandler);
 
 const GodotConfig = {
 	$GodotConfig__postset: 'Module["initConfig"] = GodotConfig.init_config;',
+	$GodotConfig__deps: ['$GodotRuntime'],
 	$GodotConfig: {
 		canvas: null,
-		locale: "en",
+		locale: 'en',
 		resize_on_start: false,
 		on_execute: null,
 
-		init_config: function(p_opts) {
-			GodotConfig.resize_on_start = p_opts['resizeCanvasOnStart'] ? true : false;
+		init_config: function (p_opts) {
+			GodotConfig.resize_on_start = !!p_opts['resizeCanvasOnStart'];
 			GodotConfig.canvas = p_opts['canvas'];
 			GodotConfig.locale = p_opts['locale'] || GodotConfig.locale;
 			GodotConfig.on_execute = p_opts['onExecute'];
 			// This is called by emscripten, even if undocumented.
-			Module['onExit'] = p_opts['onExit'];
+			Module['onExit'] = p_opts['onExit']; // eslint-disable-line no-undef
+		},
+
+		locate_file: function (file) {
+			return Module['locateFile'](file); // eslint-disable-line no-undef
 		},
 	},
 
-	godot_js_config_canvas_id_get: function(p_ptr, p_ptr_max) {
-		stringToUTF8('#' + GodotConfig.canvas.id, p_ptr, p_ptr_max);
+	godot_js_config_canvas_id_get: function (p_ptr, p_ptr_max) {
+		GodotRuntime.stringToHeap(`#${GodotConfig.canvas.id}`, p_ptr, p_ptr_max);
 	},
 
-	godot_js_config_locale_get: function(p_ptr, p_ptr_max) {
-		stringToUTF8(GodotConfig.locale, p_ptr, p_ptr_max);
+	godot_js_config_locale_get: function (p_ptr, p_ptr_max) {
+		GodotRuntime.stringToHeap(GodotConfig.locale, p_ptr, p_ptr_max);
 	},
 
-	godot_js_config_is_resize_on_start: function() {
+	godot_js_config_is_resize_on_start: function () {
 		return GodotConfig.resize_on_start ? 1 : 0;
 	},
 };
@@ -87,7 +92,7 @@ autoAddDeps(GodotConfig, '$GodotConfig');
 mergeInto(LibraryManager.library, GodotConfig);
 
 const GodotFS = {
-	$GodotFS__deps: ['$FS', '$IDBFS'],
+	$GodotFS__deps: ['$FS', '$IDBFS', '$GodotRuntime'],
 	$GodotFS__postset: [
 		'Module["initFS"] = GodotFS.init;',
 		'Module["deinitFS"] = GodotFS.deinit;',
@@ -98,7 +103,7 @@ const GodotFS = {
 		_syncing: false,
 		_mount_points: [],
 
-		is_persistent: function() {
+		is_persistent: function () {
 			return GodotFS._idbfs ? 1 : 0;
 		},
 
@@ -106,7 +111,7 @@ const GodotFS = {
 		// Returns a promise that resolves when the FS is ready.
 		// We keep track of mount_points, so that we can properly close the IDBFS
 		// since emscripten is not doing it by itself. (emscripten GH#12516).
-		init: function(persistentPaths) {
+		init: function (persistentPaths) {
 			GodotFS._idbfs = false;
 			if (!Array.isArray(persistentPaths)) {
 				return Promise.reject(new Error('Persistent paths must be an array'));
@@ -127,16 +132,16 @@ const GodotFS = {
 				}
 			}
 
-			GodotFS._mount_points.forEach(function(path) {
+			GodotFS._mount_points.forEach(function (path) {
 				createRecursive(path);
 				FS.mount(IDBFS, {}, path);
 			});
-			return new Promise(function(resolve, reject) {
-				FS.syncfs(true, function(err) {
+			return new Promise(function (resolve, reject) {
+				FS.syncfs(true, function (err) {
 					if (err) {
 						GodotFS._mount_points = [];
 						GodotFS._idbfs = false;
-						console.log("IndexedDB not available: " + err.message);
+						GodotRuntime.print(`IndexedDB not available: ${err.message}`);
 					} else {
 						GodotFS._idbfs = true;
 					}
@@ -146,12 +151,12 @@ const GodotFS = {
 		},
 
 		// Deinit godot file system, making sure to unmount file systems, and close IDBFS(s).
-		deinit: function() {
-			GodotFS._mount_points.forEach(function(path) {
+		deinit: function () {
+			GodotFS._mount_points.forEach(function (path) {
 				try {
 					FS.unmount(path);
 				} catch (e) {
-					console.log("Already unmounted", e);
+					GodotRuntime.print('Already unmounted', e);
 				}
 				if (GodotFS._idbfs && IDBFS.dbs[path]) {
 					IDBFS.dbs[path].close();
@@ -163,16 +168,16 @@ const GodotFS = {
 			GodotFS._syncing = false;
 		},
 
-		sync: function() {
+		sync: function () {
 			if (GodotFS._syncing) {
-				err('Already syncing!');
+				GodotRuntime.error('Already syncing!');
 				return Promise.resolve();
 			}
 			GodotFS._syncing = true;
 			return new Promise(function (resolve, reject) {
-				FS.syncfs(false, function(error) {
+				FS.syncfs(false, function (error) {
 					if (error) {
-						err('Failed to save IDB file system: ' + error.message);
+						GodotRuntime.error(`Failed to save IDB file system: ${error.message}`);
 					}
 					GodotFS._syncing = false;
 					resolve(error);
@@ -181,9 +186,9 @@ const GodotFS = {
 		},
 
 		// Copies a buffer to the internal file system. Creating directories recursively.
-		copy_to_fs: function(path, buffer) {
-			const idx = path.lastIndexOf("/");
-			let dir = "/";
+		copy_to_fs: function (path, buffer) {
+			const idx = path.lastIndexOf('/');
+			let dir = '/';
 			if (idx > 0) {
 				dir = path.slice(0, idx);
 			}
@@ -195,105 +200,68 @@ const GodotFS = {
 				}
 				FS.mkdirTree(dir);
 			}
-			FS.writeFile(path, new Uint8Array(buffer), {'flags': 'wx+'});
+			FS.writeFile(path, new Uint8Array(buffer), { 'flags': 'wx+' });
 		},
 	},
 };
 mergeInto(LibraryManager.library, GodotFS);
 
 const GodotOS = {
-	$GodotOS__deps: ['$GodotFS'],
+	$GodotOS__deps: ['$GodotFS', '$GodotRuntime'],
 	$GodotOS__postset: [
 		'Module["request_quit"] = function() { GodotOS.request_quit() };',
 		'GodotOS._fs_sync_promise = Promise.resolve();',
 	].join(''),
 	$GodotOS: {
-		request_quit: function() {},
+		request_quit: function () {},
 		_async_cbs: [],
 		_fs_sync_promise: null,
 
-		get_func: function(ptr) {
-			return wasmTable.get(ptr);
-		},
-
-		atexit: function(p_promise_cb) {
+		atexit: function (p_promise_cb) {
 			GodotOS._async_cbs.push(p_promise_cb);
 		},
 
-		finish_async: function(callback) {
-			GodotOS._fs_sync_promise.then(function(err) {
+		finish_async: function (callback) {
+			GodotOS._fs_sync_promise.then(function (err) {
 				const promises = [];
-				GodotOS._async_cbs.forEach(function(cb) {
+				GodotOS._async_cbs.forEach(function (cb) {
 					promises.push(new Promise(cb));
 				});
 				return Promise.all(promises);
-			}).then(function() {
+			}).then(function () {
 				return GodotFS.sync(); // Final FS sync.
-			}).then(function(err) {
+			}).then(function (err) {
 				// Always deferred.
-				setTimeout(function() {
+				setTimeout(function () {
 					callback();
 				}, 0);
 			});
 		},
-
-		allocString: function(p_str) {
-			const length = lengthBytesUTF8(p_str)+1;
-			const c_str = _malloc(length);
-			stringToUTF8(p_str, c_str, length);
-			return c_str;
-		},
-
-		allocStringArray: function(strings) {
-			const size = strings.length;
-			const c_ptr = _malloc(size * 4);
-			for (let i = 0; i < size; i++) {
-				HEAP32[(c_ptr >> 2) + i] = GodotOS.allocString(strings[i]);
-			}
-			return c_ptr;
-		},
-
-		freeStringArray: function(c_ptr, size) {
-			for (let i = 0; i < size; i++) {
-				_free(HEAP32[(c_ptr >> 2) + i]);
-			}
-			_free(c_ptr);
-		},
-
-		heapSub: function(heap, ptr, size) {
-			const bytes = heap.BYTES_PER_ELEMENT;
-			return heap.subarray(ptr / bytes, ptr / bytes + size);
-		},
-
-		heapCopy: function(heap, ptr, size) {
-			const bytes = heap.BYTES_PER_ELEMENT;
-			return heap.slice(ptr / bytes, ptr / bytes + size);
-		},
 	},
 
-	godot_js_os_finish_async: function(p_callback) {
-		const func = GodotOS.get_func(p_callback);
+	godot_js_os_finish_async: function (p_callback) {
+		const func = GodotRuntime.get_func(p_callback);
 		GodotOS.finish_async(func);
 	},
 
-	godot_js_os_request_quit_cb: function(p_callback) {
-		GodotOS.request_quit = GodotOS.get_func(p_callback);
+	godot_js_os_request_quit_cb: function (p_callback) {
+		GodotOS.request_quit = GodotRuntime.get_func(p_callback);
 	},
 
-	godot_js_os_fs_is_persistent: function() {
+	godot_js_os_fs_is_persistent: function () {
 		return GodotFS.is_persistent();
 	},
 
-	godot_js_os_fs_sync: function(callback) {
-		const func = GodotOS.get_func(callback);
+	godot_js_os_fs_sync: function (callback) {
+		const func = GodotRuntime.get_func(callback);
 		GodotOS._fs_sync_promise = GodotFS.sync();
-		GodotOS._fs_sync_promise.then(function(err) {
+		GodotOS._fs_sync_promise.then(function (err) {
 			func();
 		});
 	},
 
-	godot_js_os_execute: function(p_json) {
-		const json_args = UTF8ToString(p_json);
+	godot_js_os_execute: function (p_json) {
+		const json_args = GodotRuntime.parseString(p_json);
 		const args = JSON.parse(json_args);
 		if (GodotConfig.on_execute) {
 			GodotConfig.on_execute(args);
@@ -302,8 +270,8 @@ const GodotOS = {
 		return 1;
 	},
 
-	godot_js_os_shell_open: function(p_uri) {
-		window.open(UTF8ToString(p_uri), '_blank');
+	godot_js_os_shell_open: function (p_uri) {
+		window.open(GodotRuntime.parseString(p_uri), '_blank');
 	},
 };
 

+ 120 - 0
platform/javascript/js/libs/library_godot_runtime.js

@@ -0,0 +1,120 @@
+/*************************************************************************/
+/*  library_godot_runtime.js                                             */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+const GodotRuntime = {
+	$GodotRuntime: {
+		/*
+		 * Functions
+		 */
+		get_func: function (ptr) {
+			return wasmTable.get(ptr); // eslint-disable-line no-undef
+		},
+
+		/*
+		 * Prints
+		 */
+		error: function () {
+			err.apply(null, Array.from(arguments)); // eslint-disable-line no-undef
+		},
+
+		print: function () {
+			out.apply(null, Array.from(arguments)); // eslint-disable-line no-undef
+		},
+
+		/*
+		 * Memory
+		 */
+		malloc: function (p_size) {
+			return _malloc(p_size); // eslint-disable-line no-undef
+		},
+
+		free: function (p_ptr) {
+			_free(p_ptr); // eslint-disable-line no-undef
+		},
+
+		getHeapValue: function (p_ptr, p_type) {
+			return getValue(p_ptr, p_type); // eslint-disable-line no-undef
+		},
+
+		setHeapValue: function (p_ptr, p_value, p_type) {
+			setValue(p_ptr, p_value, p_type); // eslint-disable-line no-undef
+		},
+
+		heapSub: function (p_heap, p_ptr, p_len) {
+			const bytes = p_heap.BYTES_PER_ELEMENT;
+			return p_heap.subarray(p_ptr / bytes, p_ptr / bytes + p_len);
+		},
+
+		heapCopy: function (p_heap, p_ptr, p_len) {
+			const bytes = p_heap.BYTES_PER_ELEMENT;
+			return p_heap.slice(p_ptr / bytes, p_ptr / bytes + p_len);
+		},
+
+		/*
+		 * Strings
+		 */
+		parseString: function (p_ptr) {
+			return UTF8ToString(p_ptr); // eslint-disable-line no-undef
+		},
+
+		strlen: function (p_str) {
+			return lengthBytesUTF8(p_str); // eslint-disable-line no-undef
+		},
+
+		allocString: function (p_str) {
+			const length = GodotRuntime.strlen(p_str) + 1;
+			const c_str = GodotRuntime.malloc(length);
+			stringToUTF8(p_str, c_str, length); // eslint-disable-line no-undef
+			return c_str;
+		},
+
+		allocStringArray: function (p_strings) {
+			const size = p_strings.length;
+			const c_ptr = GodotRuntime.malloc(size * 4);
+			for (let i = 0; i < size; i++) {
+				HEAP32[(c_ptr >> 2) + i] = GodotRuntime.allocString(p_strings[i]);
+			}
+			return c_ptr;
+		},
+
+		freeStringArray: function (p_ptr, p_len) {
+			for (let i = 0; i < p_len; i++) {
+				GodotRuntime.free(HEAP32[(p_ptr >> 2) + i]);
+			}
+			GodotRuntime.free(p_ptr);
+		},
+
+		stringToHeap: function (p_str, p_ptr, p_len) {
+			return stringToUTF8Array(p_str, HEAP8, p_ptr, p_len); // eslint-disable-line no-undef
+		},
+	},
+};
+autoAddDeps(GodotRuntime, '$GodotRuntime');
+mergeInto(LibraryManager.library, GodotRuntime);

+ 1605 - 0
platform/javascript/package-lock.json

@@ -0,0 +1,1605 @@
+{
+  "name": "godot",
+  "version": "1.0.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "@babel/code-frame": {
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
+      "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
+      "dev": true,
+      "requires": {
+        "@babel/highlight": "^7.10.4"
+      }
+    },
+    "@babel/helper-validator-identifier": {
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
+      "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==",
+      "dev": true
+    },
+    "@babel/highlight": {
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
+      "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-validator-identifier": "^7.10.4",
+        "chalk": "^2.0.0",
+        "js-tokens": "^4.0.0"
+      },
+      "dependencies": {
+        "chalk": {
+          "version": "2.4.2",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+          "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^3.2.1",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^5.3.0"
+          }
+        }
+      }
+    },
+    "@eslint/eslintrc": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz",
+      "integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==",
+      "dev": true,
+      "requires": {
+        "ajv": "^6.12.4",
+        "debug": "^4.1.1",
+        "espree": "^7.3.0",
+        "globals": "^12.1.0",
+        "ignore": "^4.0.6",
+        "import-fresh": "^3.2.1",
+        "js-yaml": "^3.13.1",
+        "lodash": "^4.17.19",
+        "minimatch": "^3.0.4",
+        "strip-json-comments": "^3.1.1"
+      }
+    },
+    "@types/color-name": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
+      "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
+      "dev": true
+    },
+    "@types/json5": {
+      "version": "0.0.29",
+      "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
+      "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
+      "dev": true
+    },
+    "acorn": {
+      "version": "7.4.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz",
+      "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==",
+      "dev": true
+    },
+    "acorn-jsx": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz",
+      "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==",
+      "dev": true
+    },
+    "ajv": {
+      "version": "6.12.5",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz",
+      "integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==",
+      "dev": true,
+      "requires": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      }
+    },
+    "ansi-colors": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
+      "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+      "dev": true
+    },
+    "ansi-regex": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+      "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+      "dev": true
+    },
+    "ansi-styles": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "dev": true,
+      "requires": {
+        "color-convert": "^1.9.0"
+      }
+    },
+    "argparse": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+      "dev": true,
+      "requires": {
+        "sprintf-js": "~1.0.2"
+      }
+    },
+    "array-includes": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz",
+      "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.0",
+        "is-string": "^1.0.5"
+      },
+      "dependencies": {
+        "es-abstract": {
+          "version": "1.17.6",
+          "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+          "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+          "dev": true,
+          "requires": {
+            "es-to-primitive": "^1.2.1",
+            "function-bind": "^1.1.1",
+            "has": "^1.0.3",
+            "has-symbols": "^1.0.1",
+            "is-callable": "^1.2.0",
+            "is-regex": "^1.1.0",
+            "object-inspect": "^1.7.0",
+            "object-keys": "^1.1.1",
+            "object.assign": "^4.1.0",
+            "string.prototype.trimend": "^1.0.1",
+            "string.prototype.trimstart": "^1.0.1"
+          }
+        }
+      }
+    },
+    "array.prototype.flat": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz",
+      "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.0-next.1"
+      },
+      "dependencies": {
+        "es-abstract": {
+          "version": "1.17.6",
+          "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+          "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+          "dev": true,
+          "requires": {
+            "es-to-primitive": "^1.2.1",
+            "function-bind": "^1.1.1",
+            "has": "^1.0.3",
+            "has-symbols": "^1.0.1",
+            "is-callable": "^1.2.0",
+            "is-regex": "^1.1.0",
+            "object-inspect": "^1.7.0",
+            "object-keys": "^1.1.1",
+            "object.assign": "^4.1.0",
+            "string.prototype.trimend": "^1.0.1",
+            "string.prototype.trimstart": "^1.0.1"
+          }
+        }
+      }
+    },
+    "astral-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
+      "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
+      "dev": true
+    },
+    "balanced-match": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+      "dev": true
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "requires": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "callsites": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+      "dev": true
+    },
+    "chalk": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
+      "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "4.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
+          "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
+          "dev": true,
+          "requires": {
+            "@types/color-name": "^1.1.1",
+            "color-convert": "^2.0.1"
+          }
+        },
+        "color-convert": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+          "dev": true,
+          "requires": {
+            "color-name": "~1.1.4"
+          }
+        },
+        "color-name": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+          "dev": true
+        },
+        "has-flag": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+          "dev": true
+        },
+        "supports-color": {
+          "version": "7.2.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+          "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+          "dev": true,
+          "requires": {
+            "has-flag": "^4.0.0"
+          }
+        }
+      }
+    },
+    "color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "dev": true,
+      "requires": {
+        "color-name": "1.1.3"
+      }
+    },
+    "color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+      "dev": true
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+      "dev": true
+    },
+    "confusing-browser-globals": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz",
+      "integrity": "sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw==",
+      "dev": true
+    },
+    "contains-path": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz",
+      "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=",
+      "dev": true
+    },
+    "cross-spawn": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+      "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+      "dev": true,
+      "requires": {
+        "path-key": "^3.1.0",
+        "shebang-command": "^2.0.0",
+        "which": "^2.0.1"
+      }
+    },
+    "debug": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+      "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+      "dev": true,
+      "requires": {
+        "ms": "^2.1.1"
+      }
+    },
+    "deep-is": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+      "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+      "dev": true
+    },
+    "define-properties": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+      "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+      "dev": true,
+      "requires": {
+        "object-keys": "^1.0.12"
+      }
+    },
+    "doctrine": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+      "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+      "dev": true,
+      "requires": {
+        "esutils": "^2.0.2"
+      }
+    },
+    "emoji-regex": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+      "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+      "dev": true
+    },
+    "enquirer": {
+      "version": "2.3.6",
+      "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
+      "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==",
+      "dev": true,
+      "requires": {
+        "ansi-colors": "^4.1.1"
+      }
+    },
+    "error-ex": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+      "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+      "dev": true,
+      "requires": {
+        "is-arrayish": "^0.2.1"
+      }
+    },
+    "es-abstract": {
+      "version": "1.18.0-next.0",
+      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.0.tgz",
+      "integrity": "sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ==",
+      "dev": true,
+      "requires": {
+        "es-to-primitive": "^1.2.1",
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3",
+        "has-symbols": "^1.0.1",
+        "is-callable": "^1.2.0",
+        "is-negative-zero": "^2.0.0",
+        "is-regex": "^1.1.1",
+        "object-inspect": "^1.8.0",
+        "object-keys": "^1.1.1",
+        "object.assign": "^4.1.0",
+        "string.prototype.trimend": "^1.0.1",
+        "string.prototype.trimstart": "^1.0.1"
+      }
+    },
+    "es-to-primitive": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+      "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+      "dev": true,
+      "requires": {
+        "is-callable": "^1.1.4",
+        "is-date-object": "^1.0.1",
+        "is-symbol": "^1.0.2"
+      }
+    },
+    "escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+      "dev": true
+    },
+    "eslint": {
+      "version": "7.9.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.9.0.tgz",
+      "integrity": "sha512-V6QyhX21+uXp4T+3nrNfI3hQNBDa/P8ga7LoQOenwrlEFXrEnUEE+ok1dMtaS3b6rmLXhT1TkTIsG75HMLbknA==",
+      "dev": true,
+      "requires": {
+        "@babel/code-frame": "^7.0.0",
+        "@eslint/eslintrc": "^0.1.3",
+        "ajv": "^6.10.0",
+        "chalk": "^4.0.0",
+        "cross-spawn": "^7.0.2",
+        "debug": "^4.0.1",
+        "doctrine": "^3.0.0",
+        "enquirer": "^2.3.5",
+        "eslint-scope": "^5.1.0",
+        "eslint-utils": "^2.1.0",
+        "eslint-visitor-keys": "^1.3.0",
+        "espree": "^7.3.0",
+        "esquery": "^1.2.0",
+        "esutils": "^2.0.2",
+        "file-entry-cache": "^5.0.1",
+        "functional-red-black-tree": "^1.0.1",
+        "glob-parent": "^5.0.0",
+        "globals": "^12.1.0",
+        "ignore": "^4.0.6",
+        "import-fresh": "^3.0.0",
+        "imurmurhash": "^0.1.4",
+        "is-glob": "^4.0.0",
+        "js-yaml": "^3.13.1",
+        "json-stable-stringify-without-jsonify": "^1.0.1",
+        "levn": "^0.4.1",
+        "lodash": "^4.17.19",
+        "minimatch": "^3.0.4",
+        "natural-compare": "^1.4.0",
+        "optionator": "^0.9.1",
+        "progress": "^2.0.0",
+        "regexpp": "^3.1.0",
+        "semver": "^7.2.1",
+        "strip-ansi": "^6.0.0",
+        "strip-json-comments": "^3.1.0",
+        "table": "^5.2.3",
+        "text-table": "^0.2.0",
+        "v8-compile-cache": "^2.0.3"
+      }
+    },
+    "eslint-config-airbnb-base": {
+      "version": "14.2.0",
+      "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.0.tgz",
+      "integrity": "sha512-Snswd5oC6nJaevs3nZoLSTvGJBvzTfnBqOIArkf3cbyTyq9UD79wOk8s+RiL6bhca0p/eRO6veczhf6A/7Jy8Q==",
+      "dev": true,
+      "requires": {
+        "confusing-browser-globals": "^1.0.9",
+        "object.assign": "^4.1.0",
+        "object.entries": "^1.1.2"
+      }
+    },
+    "eslint-import-resolver-node": {
+      "version": "0.3.4",
+      "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz",
+      "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==",
+      "dev": true,
+      "requires": {
+        "debug": "^2.6.9",
+        "resolve": "^1.13.1"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        }
+      }
+    },
+    "eslint-module-utils": {
+      "version": "2.6.0",
+      "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz",
+      "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==",
+      "dev": true,
+      "requires": {
+        "debug": "^2.6.9",
+        "pkg-dir": "^2.0.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        }
+      }
+    },
+    "eslint-plugin-import": {
+      "version": "2.22.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz",
+      "integrity": "sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg==",
+      "dev": true,
+      "requires": {
+        "array-includes": "^3.1.1",
+        "array.prototype.flat": "^1.2.3",
+        "contains-path": "^0.1.0",
+        "debug": "^2.6.9",
+        "doctrine": "1.5.0",
+        "eslint-import-resolver-node": "^0.3.3",
+        "eslint-module-utils": "^2.6.0",
+        "has": "^1.0.3",
+        "minimatch": "^3.0.4",
+        "object.values": "^1.1.1",
+        "read-pkg-up": "^2.0.0",
+        "resolve": "^1.17.0",
+        "tsconfig-paths": "^3.9.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "doctrine": {
+          "version": "1.5.0",
+          "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
+          "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
+          "dev": true,
+          "requires": {
+            "esutils": "^2.0.2",
+            "isarray": "^1.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        }
+      }
+    },
+    "eslint-scope": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+      "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+      "dev": true,
+      "requires": {
+        "esrecurse": "^4.3.0",
+        "estraverse": "^4.1.1"
+      }
+    },
+    "eslint-utils": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
+      "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
+      "dev": true,
+      "requires": {
+        "eslint-visitor-keys": "^1.1.0"
+      }
+    },
+    "eslint-visitor-keys": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+      "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+      "dev": true
+    },
+    "espree": {
+      "version": "7.3.0",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz",
+      "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==",
+      "dev": true,
+      "requires": {
+        "acorn": "^7.4.0",
+        "acorn-jsx": "^5.2.0",
+        "eslint-visitor-keys": "^1.3.0"
+      }
+    },
+    "esprima": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+      "dev": true
+    },
+    "esquery": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz",
+      "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==",
+      "dev": true,
+      "requires": {
+        "estraverse": "^5.1.0"
+      },
+      "dependencies": {
+        "estraverse": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
+          "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
+          "dev": true
+        }
+      }
+    },
+    "esrecurse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+      "dev": true,
+      "requires": {
+        "estraverse": "^5.2.0"
+      },
+      "dependencies": {
+        "estraverse": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
+          "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
+          "dev": true
+        }
+      }
+    },
+    "estraverse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+      "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+      "dev": true
+    },
+    "esutils": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+      "dev": true
+    },
+    "fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+      "dev": true
+    },
+    "fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+      "dev": true
+    },
+    "fast-levenshtein": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+      "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+      "dev": true
+    },
+    "file-entry-cache": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
+      "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
+      "dev": true,
+      "requires": {
+        "flat-cache": "^2.0.1"
+      }
+    },
+    "find-up": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+      "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+      "dev": true,
+      "requires": {
+        "locate-path": "^2.0.0"
+      }
+    },
+    "flat-cache": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
+      "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
+      "dev": true,
+      "requires": {
+        "flatted": "^2.0.0",
+        "rimraf": "2.6.3",
+        "write": "1.0.3"
+      }
+    },
+    "flatted": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
+      "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
+      "dev": true
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+      "dev": true
+    },
+    "function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+      "dev": true
+    },
+    "functional-red-black-tree": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+      "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
+      "dev": true
+    },
+    "glob": {
+      "version": "7.1.6",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+      "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+      "dev": true,
+      "requires": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.0.4",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      }
+    },
+    "glob-parent": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
+      "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
+      "dev": true,
+      "requires": {
+        "is-glob": "^4.0.1"
+      }
+    },
+    "globals": {
+      "version": "12.4.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
+      "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
+      "dev": true,
+      "requires": {
+        "type-fest": "^0.8.1"
+      }
+    },
+    "graceful-fs": {
+      "version": "4.2.4",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
+      "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
+      "dev": true
+    },
+    "has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "dev": true,
+      "requires": {
+        "function-bind": "^1.1.1"
+      }
+    },
+    "has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+      "dev": true
+    },
+    "has-symbols": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
+      "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
+      "dev": true
+    },
+    "hosted-git-info": {
+      "version": "2.8.8",
+      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
+      "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
+      "dev": true
+    },
+    "ignore": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+      "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+      "dev": true
+    },
+    "import-fresh": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
+      "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==",
+      "dev": true,
+      "requires": {
+        "parent-module": "^1.0.0",
+        "resolve-from": "^4.0.0"
+      }
+    },
+    "imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+      "dev": true
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "dev": true,
+      "requires": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "dev": true
+    },
+    "is-arrayish": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+      "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+      "dev": true
+    },
+    "is-callable": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.1.tgz",
+      "integrity": "sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg==",
+      "dev": true
+    },
+    "is-date-object": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz",
+      "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==",
+      "dev": true
+    },
+    "is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+      "dev": true
+    },
+    "is-fullwidth-code-point": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+      "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+      "dev": true
+    },
+    "is-glob": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+      "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+      "dev": true,
+      "requires": {
+        "is-extglob": "^2.1.1"
+      }
+    },
+    "is-negative-zero": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz",
+      "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=",
+      "dev": true
+    },
+    "is-regex": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
+      "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==",
+      "dev": true,
+      "requires": {
+        "has-symbols": "^1.0.1"
+      }
+    },
+    "is-string": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz",
+      "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==",
+      "dev": true
+    },
+    "is-symbol": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
+      "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
+      "dev": true,
+      "requires": {
+        "has-symbols": "^1.0.1"
+      }
+    },
+    "isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+      "dev": true
+    },
+    "isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+      "dev": true
+    },
+    "js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "dev": true
+    },
+    "js-yaml": {
+      "version": "3.14.0",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
+      "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
+      "dev": true,
+      "requires": {
+        "argparse": "^1.0.7",
+        "esprima": "^4.0.0"
+      }
+    },
+    "json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
+    "json-stable-stringify-without-jsonify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+      "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+      "dev": true
+    },
+    "json5": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+      "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+      "dev": true,
+      "requires": {
+        "minimist": "^1.2.0"
+      }
+    },
+    "levn": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+      "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+      "dev": true,
+      "requires": {
+        "prelude-ls": "^1.2.1",
+        "type-check": "~0.4.0"
+      }
+    },
+    "load-json-file": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
+      "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "parse-json": "^2.2.0",
+        "pify": "^2.0.0",
+        "strip-bom": "^3.0.0"
+      }
+    },
+    "locate-path": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+      "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+      "dev": true,
+      "requires": {
+        "p-locate": "^2.0.0",
+        "path-exists": "^3.0.0"
+      }
+    },
+    "lodash": {
+      "version": "4.17.20",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
+      "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
+      "dev": true
+    },
+    "minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "dev": true,
+      "requires": {
+        "brace-expansion": "^1.1.7"
+      }
+    },
+    "minimist": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+      "dev": true
+    },
+    "mkdirp": {
+      "version": "0.5.5",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+      "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+      "dev": true,
+      "requires": {
+        "minimist": "^1.2.5"
+      }
+    },
+    "ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "dev": true
+    },
+    "natural-compare": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+      "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+      "dev": true
+    },
+    "normalize-package-data": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+      "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+      "dev": true,
+      "requires": {
+        "hosted-git-info": "^2.1.4",
+        "resolve": "^1.10.0",
+        "semver": "2 || 3 || 4 || 5",
+        "validate-npm-package-license": "^3.0.1"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
+    "object-inspect": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz",
+      "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==",
+      "dev": true
+    },
+    "object-keys": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+      "dev": true
+    },
+    "object.assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz",
+      "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.18.0-next.0",
+        "has-symbols": "^1.0.1",
+        "object-keys": "^1.1.1"
+      }
+    },
+    "object.entries": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz",
+      "integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.5",
+        "has": "^1.0.3"
+      },
+      "dependencies": {
+        "es-abstract": {
+          "version": "1.17.6",
+          "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+          "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+          "dev": true,
+          "requires": {
+            "es-to-primitive": "^1.2.1",
+            "function-bind": "^1.1.1",
+            "has": "^1.0.3",
+            "has-symbols": "^1.0.1",
+            "is-callable": "^1.2.0",
+            "is-regex": "^1.1.0",
+            "object-inspect": "^1.7.0",
+            "object-keys": "^1.1.1",
+            "object.assign": "^4.1.0",
+            "string.prototype.trimend": "^1.0.1",
+            "string.prototype.trimstart": "^1.0.1"
+          }
+        }
+      }
+    },
+    "object.values": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz",
+      "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.0-next.1",
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3"
+      },
+      "dependencies": {
+        "es-abstract": {
+          "version": "1.17.6",
+          "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+          "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+          "dev": true,
+          "requires": {
+            "es-to-primitive": "^1.2.1",
+            "function-bind": "^1.1.1",
+            "has": "^1.0.3",
+            "has-symbols": "^1.0.1",
+            "is-callable": "^1.2.0",
+            "is-regex": "^1.1.0",
+            "object-inspect": "^1.7.0",
+            "object-keys": "^1.1.1",
+            "object.assign": "^4.1.0",
+            "string.prototype.trimend": "^1.0.1",
+            "string.prototype.trimstart": "^1.0.1"
+          }
+        }
+      }
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "dev": true,
+      "requires": {
+        "wrappy": "1"
+      }
+    },
+    "optionator": {
+      "version": "0.9.1",
+      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+      "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+      "dev": true,
+      "requires": {
+        "deep-is": "^0.1.3",
+        "fast-levenshtein": "^2.0.6",
+        "levn": "^0.4.1",
+        "prelude-ls": "^1.2.1",
+        "type-check": "^0.4.0",
+        "word-wrap": "^1.2.3"
+      }
+    },
+    "p-limit": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+      "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+      "dev": true,
+      "requires": {
+        "p-try": "^1.0.0"
+      }
+    },
+    "p-locate": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+      "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+      "dev": true,
+      "requires": {
+        "p-limit": "^1.1.0"
+      }
+    },
+    "p-try": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+      "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
+      "dev": true
+    },
+    "parent-module": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+      "dev": true,
+      "requires": {
+        "callsites": "^3.0.0"
+      }
+    },
+    "parse-json": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+      "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+      "dev": true,
+      "requires": {
+        "error-ex": "^1.2.0"
+      }
+    },
+    "path-exists": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+      "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+      "dev": true
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+      "dev": true
+    },
+    "path-key": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+      "dev": true
+    },
+    "path-parse": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+      "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+      "dev": true
+    },
+    "path-type": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
+      "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
+      "dev": true,
+      "requires": {
+        "pify": "^2.0.0"
+      }
+    },
+    "pify": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+      "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+      "dev": true
+    },
+    "pkg-dir": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
+      "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=",
+      "dev": true,
+      "requires": {
+        "find-up": "^2.1.0"
+      }
+    },
+    "prelude-ls": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+      "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+      "dev": true
+    },
+    "progress": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+      "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+      "dev": true
+    },
+    "punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+      "dev": true
+    },
+    "read-pkg": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
+      "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
+      "dev": true,
+      "requires": {
+        "load-json-file": "^2.0.0",
+        "normalize-package-data": "^2.3.2",
+        "path-type": "^2.0.0"
+      }
+    },
+    "read-pkg-up": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
+      "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
+      "dev": true,
+      "requires": {
+        "find-up": "^2.0.0",
+        "read-pkg": "^2.0.0"
+      }
+    },
+    "regexpp": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
+      "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==",
+      "dev": true
+    },
+    "resolve": {
+      "version": "1.17.0",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
+      "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
+      "dev": true,
+      "requires": {
+        "path-parse": "^1.0.6"
+      }
+    },
+    "resolve-from": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+      "dev": true
+    },
+    "rimraf": {
+      "version": "2.6.3",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+      "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+      "dev": true,
+      "requires": {
+        "glob": "^7.1.3"
+      }
+    },
+    "semver": {
+      "version": "7.3.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
+      "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
+      "dev": true
+    },
+    "shebang-command": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+      "dev": true,
+      "requires": {
+        "shebang-regex": "^3.0.0"
+      }
+    },
+    "shebang-regex": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+      "dev": true
+    },
+    "slice-ansi": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
+      "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^3.2.0",
+        "astral-regex": "^1.0.0",
+        "is-fullwidth-code-point": "^2.0.0"
+      }
+    },
+    "spdx-correct": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
+      "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==",
+      "dev": true,
+      "requires": {
+        "spdx-expression-parse": "^3.0.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "spdx-exceptions": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
+      "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
+      "dev": true
+    },
+    "spdx-expression-parse": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+      "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
+      "dev": true,
+      "requires": {
+        "spdx-exceptions": "^2.1.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "spdx-license-ids": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz",
+      "integrity": "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==",
+      "dev": true
+    },
+    "sprintf-js": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+      "dev": true
+    },
+    "string-width": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+      "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+      "dev": true,
+      "requires": {
+        "emoji-regex": "^7.0.1",
+        "is-fullwidth-code-point": "^2.0.0",
+        "strip-ansi": "^5.1.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+          "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+          "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^4.1.0"
+          }
+        }
+      }
+    },
+    "string.prototype.trimend": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz",
+      "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.5"
+      },
+      "dependencies": {
+        "es-abstract": {
+          "version": "1.17.6",
+          "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+          "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+          "dev": true,
+          "requires": {
+            "es-to-primitive": "^1.2.1",
+            "function-bind": "^1.1.1",
+            "has": "^1.0.3",
+            "has-symbols": "^1.0.1",
+            "is-callable": "^1.2.0",
+            "is-regex": "^1.1.0",
+            "object-inspect": "^1.7.0",
+            "object-keys": "^1.1.1",
+            "object.assign": "^4.1.0",
+            "string.prototype.trimend": "^1.0.1",
+            "string.prototype.trimstart": "^1.0.1"
+          }
+        }
+      }
+    },
+    "string.prototype.trimstart": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz",
+      "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.5"
+      },
+      "dependencies": {
+        "es-abstract": {
+          "version": "1.17.6",
+          "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+          "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+          "dev": true,
+          "requires": {
+            "es-to-primitive": "^1.2.1",
+            "function-bind": "^1.1.1",
+            "has": "^1.0.3",
+            "has-symbols": "^1.0.1",
+            "is-callable": "^1.2.0",
+            "is-regex": "^1.1.0",
+            "object-inspect": "^1.7.0",
+            "object-keys": "^1.1.1",
+            "object.assign": "^4.1.0",
+            "string.prototype.trimend": "^1.0.1",
+            "string.prototype.trimstart": "^1.0.1"
+          }
+        }
+      }
+    },
+    "strip-ansi": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+      "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+      "dev": true,
+      "requires": {
+        "ansi-regex": "^5.0.0"
+      }
+    },
+    "strip-bom": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+      "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+      "dev": true
+    },
+    "strip-json-comments": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+      "dev": true
+    },
+    "supports-color": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+      "dev": true,
+      "requires": {
+        "has-flag": "^3.0.0"
+      }
+    },
+    "table": {
+      "version": "5.4.6",
+      "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
+      "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
+      "dev": true,
+      "requires": {
+        "ajv": "^6.10.2",
+        "lodash": "^4.17.14",
+        "slice-ansi": "^2.1.0",
+        "string-width": "^3.0.0"
+      }
+    },
+    "text-table": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+      "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+      "dev": true
+    },
+    "tsconfig-paths": {
+      "version": "3.9.0",
+      "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz",
+      "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==",
+      "dev": true,
+      "requires": {
+        "@types/json5": "^0.0.29",
+        "json5": "^1.0.1",
+        "minimist": "^1.2.0",
+        "strip-bom": "^3.0.0"
+      }
+    },
+    "type-check": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+      "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+      "dev": true,
+      "requires": {
+        "prelude-ls": "^1.2.1"
+      }
+    },
+    "type-fest": {
+      "version": "0.8.1",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+      "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+      "dev": true
+    },
+    "uri-js": {
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz",
+      "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==",
+      "dev": true,
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "v8-compile-cache": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz",
+      "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==",
+      "dev": true
+    },
+    "validate-npm-package-license": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+      "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+      "dev": true,
+      "requires": {
+        "spdx-correct": "^3.0.0",
+        "spdx-expression-parse": "^3.0.0"
+      }
+    },
+    "which": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+      "dev": true,
+      "requires": {
+        "isexe": "^2.0.0"
+      }
+    },
+    "word-wrap": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+      "dev": true
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+      "dev": true
+    },
+    "write": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
+      "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
+      "dev": true,
+      "requires": {
+        "mkdirp": "^0.5.1"
+      }
+    }
+  }
+}

+ 24 - 0
platform/javascript/package.json

@@ -0,0 +1,24 @@
+{
+  "name": "godot",
+  "private": true,
+  "version": "1.0.0",
+  "description": "Linting setup for Godot's HTML5 platform code",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1",
+    "lint": "npm run lint:engine && npm run lint:libs && npm run lint:modules",
+    "lint:engine": "eslint \"js/engine/*.js\" --no-eslintrc -c .eslintrc.engine.js",
+    "lint:libs": "eslint \"js/libs/*.js\" --no-eslintrc -c .eslintrc.libs.js",
+    "lint:modules": "eslint \"../../modules/**/*.js\" --no-eslintrc -c .eslintrc.libs.js",
+    "format": "npm run format:engine && npm run format:libs && npm run format:modules",
+    "format:engine": "npm run lint:engine -- --fix",
+    "format:libs": "npm run lint:libs -- --fix",
+    "format:modules": "npm run lint:modules -- --fix"
+  },
+  "author": "Godot Engine contributors",
+  "license": "MIT",
+  "devDependencies": {
+    "eslint": "^7.9.0",
+    "eslint-config-airbnb-base": "^14.2.0",
+    "eslint-plugin-import": "^2.22.0"
+  }
+}