Enable more ruff checks. NFC
diff --git a/bazel/emscripten_toolchain/link_wrapper.py b/bazel/emscripten_toolchain/link_wrapper.py
index 4e9a150..e473206 100644
--- a/bazel/emscripten_toolchain/link_wrapper.py
+++ b/bazel/emscripten_toolchain/link_wrapper.py
@@ -31,7 +31,7 @@
   with open(new_param_filename, 'w') as f:
     for param in param_file_args:
       if ' ' in param:
-        f.write('"%s"' % param)
+        f.write(f'"{param}"')
       else:
         f.write(param)
       f.write('\n')
@@ -116,7 +116,7 @@
 
     # Next we need to convert length of the filename to LEB128.
     # Start by converting the length of the filename to a bit string.
-    bit_string = '{0:b}'.format(len(base_name + '.wasm.debug.wasm'))
+    bit_string = f"{len(base_name + '.wasm.debug.wasm'):b}"
 
     # Pad the bit string with 0s so that its length is a multiple of 7.
     while len(bit_string) % 7 != 0:
diff --git a/bazel/emscripten_toolchain/wasm_binary.py b/bazel/emscripten_toolchain/wasm_binary.py
index d7d6142..413e238 100644
--- a/bazel/emscripten_toolchain/wasm_binary.py
+++ b/bazel/emscripten_toolchain/wasm_binary.py
@@ -49,7 +49,7 @@
     if args.allow_empty_outputs:
       ensure(output)
     else:
-      print("[ERROR] Archive does not contain file with extname: %s" % extname)
+      print(f"[ERROR] Archive does not contain file with extname: {extname}")
 
 
 if __name__ == '__main__':
diff --git a/emsdk.py b/emsdk.py
index 9331135..ed55063 100644
--- a/emsdk.py
+++ b/emsdk.py
@@ -72,7 +72,7 @@
 
 
 def exit_with_error(msg):
-  errlog('error: %s' % msg)
+  errlog(f'error: {msg}')
   sys.exit(1)
 
 
@@ -255,7 +255,7 @@
   try:
     # The "-products *" allows detection of Build Tools, the "-prerelease" allows detection of Preview version
     # of Visual Studio and Build Tools.
-    stdout = run_get_output([vswhere_path, '-latest', '-products', '*', '-prerelease', '-version', '[%s.0,%s.0)' % (version, version + 1), '-requires', 'Microsoft.VisualStudio.Component.VC.Tools.' + tools_arch, '-property', 'installationPath', '-format', 'json'])
+    stdout = run_get_output([vswhere_path, '-latest', '-products', '*', '-prerelease', '-version', f'[{version}.0,{version + 1}.0)', '-requires', 'Microsoft.VisualStudio.Component.VC.Tools.' + tools_arch, '-property', 'installationPath', '-format', 'json'])
     json_output = json.loads(stdout)
     return str(json_output[0]['installationPath'])
   except Exception:
@@ -647,7 +647,7 @@
 
 
 def download_with_curl(url, file_name):
-  print("Downloading: %s from %s" % (file_name, url))
+  print(f"Downloading: {file_name} from {url}")
   if not shutil.which('curl'):
     exit_with_error('curl not found in PATH')
   # -#: show progress bar
@@ -660,9 +660,9 @@
   u = urlopen(url)
   file_size = get_content_length(u)
   if file_size > 0:
-    print("Downloading: %s from %s, %s Bytes" % (file_name, url, file_size))
+    print(f"Downloading: {file_name} from {url}, {file_size} Bytes")
   else:
-    print("Downloading: %s from %s" % (file_name, url))
+    print(f"Downloading: {file_name} from {url}")
 
   file_size_dl = 0
   # Draw a progress bar 80 chars wide (in non-TTY mode)
@@ -683,7 +683,7 @@
         if file_size:
             percent = file_size_dl * 100.0 / file_size
             if TTY_OUTPUT:
-                status = r" %10d  [%3.02f%%]" % (file_size_dl, percent)
+                status = f' {file_size_dl:10d}  [{percent:3.02f}%]'
                 print(status, end='\r')
             else:
                 while progress_shown < progress_max * percent / 100:
@@ -695,7 +695,7 @@
     print(']')
     sys.stdout.flush()
 
-  debug_print('finished downloading (%d bytes)' % file_size_dl)
+  debug_print(f'finished downloading ({file_size_dl} bytes)')
 
 
 def download_file(url, dstpath, filename_prefix=''):
@@ -1217,11 +1217,11 @@
           os.chmod(dst, os.stat(dst).st_mode | stat.S_IEXEC)
 
     cache_dir = os.path.join(root, 'cache')
-    write_file(os.path.join(root, 'emcc_ccache.conf'), '''\
+    write_file(os.path.join(root, 'emcc_ccache.conf'), f'''\
 # Set maximum cache size to 10 GB:
 max_size = 10G
-cache_dir = %s
-''' % cache_dir)
+cache_dir = {cache_dir}
+''')
     mkdir_p(cache_dir)
 
   return success
@@ -1464,7 +1464,7 @@
       subprocess.check_call([sys.executable, os.path.join(directory, 'bootstrap.py')],
                             cwd=directory, stdin=subprocess.DEVNULL, env=env, text=True)
     except subprocess.CalledProcessError as e:
-      errlog('Error running %s' % str(e))
+      errlog(f'Error running {e!s}')
       return False
 
     print('Done running: Emscripten bootstrap')
@@ -1803,9 +1803,9 @@
       print('    ' + p)
     print('- This can be done for the current shell by running:')
     emsdk_env, shell_config_file = get_emsdk_shell_env_configs()
-    print('    source "%s"' % emsdk_env)
+    print(f'    source "{emsdk_env}"')
     print('- Configure emsdk in your shell startup scripts by running:')
-    print('    echo \'source "%s"\' >> %s' % (emsdk_env, shell_config_file))
+    print(f'    echo \'source "{emsdk_env}"\' >> {shell_config_file}')
 
 
 class Tool:
@@ -2254,20 +2254,20 @@
   releases_info = load_releases_info()
   while name in releases_info['aliases']:
     if verbose:
-      print("Resolving SDK alias '%s' to '%s'" % (name, releases_info['aliases'][name]))
+      print(f"Resolving SDK alias '{name}' to '{releases_info['aliases'][name]}'")
     name = releases_info['aliases'][name]
   return name
 
 
 def find_latest_sdk():
-  return 'sdk-releases-%s-64bit' % (find_latest_hash())
+  return f'sdk-releases-{find_latest_hash()}-64bit'
 
 
 def find_tot_sdk():
   debug_print('Fetching emscripten-releases repository...')
   global extra_release_tag
   extra_release_tag = get_emscripten_releases_tot()
-  return 'sdk-releases-%s-64bit' % (extra_release_tag)
+  return f'sdk-releases-{extra_release_tag}-64bit'
 
 
 def parse_emscripten_version(emscripten_root):
@@ -2567,7 +2567,7 @@
 
   for tool in tools_to_activate:
     if not tool.is_installed():
-      exit_with_error("error: tool is not installed and therefore cannot be activated: '%s'" % tool)
+      exit_with_error(f"error: tool is not installed and therefore cannot be activated: '{tool}'")
 
   # Remove conflicting tools
   i = 0
@@ -2765,15 +2765,15 @@
 
 def unset_env(key):
   if POWERSHELL:
-    return 'Remove-Item env:%s -ErrorAction SilentlyContinue\n' % key
+    return f'Remove-Item env:{key} -ErrorAction SilentlyContinue\n'
   if CMD:
-    return 'set %s=\n' % key
+    return f'set {key}=\n'
   if CSH:
-    return 'unsetenv %s;\n' % key
+    return f'unsetenv {key};\n'
   if FISH:
-    return 'set -e %s;\n' % key
+    return f'set -e {key};\n'
   if BASH:
-    return 'unset %s;\n' % key
+    return f'unset {key};\n'
   assert False
 
 
@@ -2818,7 +2818,7 @@
   for key in os.environ:
     if key.startswith('EMSDK_') or key in {'EM_CACHE', 'EM_CONFIG'}:
       if key not in env_keys_to_add and key not in ignore_keys:
-        info('Clearing existing environment variable: %s' % key)
+        info(f'Clearing existing environment variable: {key}')
         env_string += unset_env(key)
 
   return env_string
@@ -2826,9 +2826,9 @@
 
 def error_on_missing_tool(name):
   if name.endswith('-64bit') and not is_os_64bit():
-    exit_with_error("'%s' is only provided for 64-bit OSes" % name)
+    exit_with_error(f"'{name}' is only provided for 64-bit OSes")
   else:
-    exit_with_error("tool or SDK not found: '%s'" % name)
+    exit_with_error(f"tool or SDK not found: '{name}'")
 
 
 def expand_sdk_name(name, activating):
@@ -2846,7 +2846,7 @@
       installed = get_installed_sdk_version()
       if installed:
         debug_print('activating currently installed SDK; not updating tot version')
-        return 'sdk-releases-%s-64bit' % installed
+        return f'sdk-releases-{installed}-64bit'
     return find_tot_sdk()
 
   if '-upstream' in name:
@@ -2866,14 +2866,14 @@
   release_hash = get_release_hash(version, releases_info)
   if release_hash:
     # Known release hash
-    full_name = '%sreleases-%s-64bit' % (sdk, release_hash)
-    print("Resolving SDK version '%s' to '%s'" % (version, full_name))
+    full_name = f'{sdk}releases-{release_hash}-64bit'
+    print(f"Resolving SDK version '{version}' to '{full_name}'")
     return full_name
 
   if len(version) == 40:
     global extra_release_tag
     extra_release_tag = version
-    return '%sreleases-%s-64bit' % (sdk, version)
+    return f'{sdk}releases-{version}-64bit'
 
   return name
 
@@ -2883,7 +2883,7 @@
     errlog("Missing command; Type 'emsdk help' to get a list of commands.")
     return 1
 
-  debug_print('emsdk.py running under `%s`' % sys.executable)
+  debug_print(f'emsdk.py running under `{sys.executable}`')
   cmd = args.pop(0)
 
   if cmd in {'help', '--help', '-h'}:
@@ -3108,13 +3108,13 @@
       return 'INSTALLED' if sdk and sdk.is_installed() else ''
 
     if (LINUX or MACOS or WINDOWS) and ARCH in {'x86', 'x86_64'}:
-      print('The *recommended* precompiled SDK download is %s (%s).' % (find_latest_version(), find_latest_hash()))
+      print(f'The *recommended* precompiled SDK download is {find_latest_version()} ({find_latest_hash()}).')
       print()
       print('To install/activate it use:')
       print('         latest')
       print('')
       print('This is equivalent to installing/activating:')
-      print('         %s             %s' % (find_latest_version(), installed_sdk_text(find_latest_sdk())))
+      print(f'         {find_latest_version()}             {installed_sdk_text(find_latest_sdk())}')
       print('')
     else:
       print('Warning: your platform does not have precompiled SDKs available.')
@@ -3125,7 +3125,8 @@
     releases_versions = sorted(load_releases_versions(), key=version_key, reverse=True)
     releases_info = load_releases_info()['releases']
     for ver in releases_versions:
-      print('         %s    %s' % (ver, installed_sdk_text('sdk-releases-%s-64bit' % get_release_hash(ver, releases_info))))
+      sdk_name = f'sdk-releases-{get_release_hash(ver, releases_info)}-64bit'
+      print(f'         {ver}    {installed_sdk_text(sdk_name)}')
     print()
 
     # Use array to work around the lack of being able to mutate from enclosing
@@ -3146,10 +3147,10 @@
         for sdk in s:
           installed = '\tINSTALLED' if sdk.is_installed() else ''
           active = '*' if sdk.is_active() else ' '
-          print('    ' + active + '    {0: <25}'.format(str(sdk)) + installed)
+          print(f'    {active}    {sdk!s: <25}{installed}')
           if arg_deps:
             for dep in sdk.deps:
-              print('          - {0: <25}'.format(dep))
+              print(f'          - {dep: <25}')
         print('')
       print('The additional following precompiled SDKs are also available for download:')
       print_sdks(find_sdks(False))
@@ -3185,7 +3186,7 @@
             has_partially_active_tools[0] = has_partially_active_tools[0] or True
           else:
             active = '   '
-          print('    ' + active + '    {0: <25}'.format(str(tool)) + installed)
+          print(f'    {active}    {tool!s: <25}{installed}')
         print('')
 
       print('The following precompiled tool packages are available for download:')
diff --git a/pyproject.toml b/pyproject.toml
index 28e412f..a480e57 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -47,16 +47,8 @@
   "subprocess-run-without-check",
   "unspecified-encoding",
   "unraw-re-pattern", # https://docs.astral.sh/ruff/rules/unraw-re-pattern/
-  "format-literals", # TODO
-  "printf-string-formatting", # TODO
-  "f-string", # TODO
   "too-many-statements-in-try-clause",
 ]
 lint.mccabe.max-complexity = 41 # Recommended: 10
-lint.pylint.allow-magic-value-types = [
-  "bytes",
-  "float",
-  "int",
-  "str",
-]
+lint.pylint.allow-magic-value-types = [ "int", "str" ]
 lint.pylint.max-branches = 29 # Recommended: 12
diff --git a/scripts/create_release.py b/scripts/create_release.py
index 64eccaf..03af2ee 100755
--- a/scripts/create_release.py
+++ b/scripts/create_release.py
@@ -48,7 +48,7 @@
   else:
     new_hash = emsdk.get_emscripten_releases_tot()
 
-  print('Creating new release: %s -> %s' % (new_version, new_hash))
+  print(f'Creating new release: {new_version} -> {new_hash}')
   release_info['releases'][new_version] = new_hash
   if asserts_hash:
     asserts_name = new_version + '-asserts'
@@ -80,7 +80,7 @@
     # Create auto-generated changes to the new git branch
     subprocess.check_call(['git', 'add', '-u', '.'], cwd=root_dir)
     subprocess.check_call(['git', 'commit', '-m', new_version], cwd=root_dir)
-    print('New release created in branch: `%s`' % branch_name)
+    print(f'New release created in branch: `{branch_name}`')
 
     # Push new branch to origin
     subprocess.check_call(['git', 'push', 'origin', branch_name], cwd=root_dir)
diff --git a/scripts/update_node.py b/scripts/update_node.py
index d066cde..2f99d02 100755
--- a/scripts/update_node.py
+++ b/scripts/update_node.py
@@ -38,7 +38,7 @@
   args = parser.parse_args()
 
   for suffix in suffixes:
-    filename = 'node-v%s%s' % (version, suffix)
+    filename = f'node-v{version}{suffix}'
     download_url = base + filename
     print('Downloading: ' + download_url)
     urllib.request.urlretrieve(download_url, filename)
diff --git a/scripts/update_python.py b/scripts/update_python.py
index 7aa6cfe..62d9f26 100755
--- a/scripts/update_python.py
+++ b/scripts/update_python.py
@@ -78,7 +78,7 @@
     check_call([python_exe, '-m', 'pip', 'install', PSUTIL])
 
     check_call([*zip_cmd(), os.path.join('..', '..', out_filename), '.'], cwd=src_dir)
-    print('Created: %s' % out_filename)
+    print(f'Created: {out_filename}')
 
     # cleanup if everything went fine
     shutil.rmtree('python-nuget')
@@ -154,19 +154,19 @@
     # process quits.
     check_call([pybin, pip, 'install', PSUTIL])
 
-    dirname = 'python-%s-%s' % (version, revision)
+    dirname = f'python-{version}-{revision}'
     if os.path.isdir(dirname):
         print('Erasing old build directory ' + dirname)
         shutil.rmtree(dirname)
     os.rename(os.path.join(install_dir, 'usr', 'local'), dirname)
-    tarball = 'python-%s-%s-%s.tar.gz' % (version, revision, osname)
+    tarball = f'python-{version}-{revision}-{osname}.tar.gz'
     shutil.rmtree(os.path.join(dirname, 'lib', 'python' + major_minor_version, 'test'))
     shutil.rmtree(os.path.join(dirname, 'include'))
     for lib in glob.glob(os.path.join(dirname, 'lib', 'lib*.a')):
         os.remove(lib)
     check_call(['tar', 'zcvf', tarball, dirname])
 
-    print('Created: %s' % tarball)
+    print(f'Created: {tarball}')
     if '--upload' in sys.argv:
       print('Uploading: ' + upload_base + tarball)
       check_call(['gsutil', 'cp', '-n', tarball, upload_base + tarball])
diff --git a/test/test.py b/test/test.py
index 987938d..29a67d7 100755
--- a/test/test.py
+++ b/test/test.py
@@ -40,13 +40,13 @@
 def check_call(cmd, **kwargs):
   if type(cmd) is not list:
     cmd = cmd.split()
-  print('running: %s' % cmd)
+  print(f'running: {cmd}')
   subprocess.run(cmd, check=True, text=True, **kwargs)
 
 
 def checked_call_with_output(cmd, expected=None, unexpected=None, stderr=None, env=None):
   cmd = cmd.split(' ')
-  print('running: %s' % cmd)
+  print(f'running: {cmd}')
   try:
     stdout = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=stderr, check=True, text=True, env=env).stdout
   except subprocess.CalledProcessError as e:
@@ -70,7 +70,7 @@
     print('warning: skipping part of failing_call_with_output() due to error codes not being propagated (see #592)')
   else:
     assert proc.returncode, 'call must have failed: ' + str([stdout, '\n========\n', stderr])
-  assert expected in stdout or expected in stderr, 'call did not have the expected output: %s: %s' % (expected, str([stdout, '\n========\n', stderr]))
+  assert expected in stdout or expected in stderr, 'call did not have the expected output: {}: {}'.format(expected, str([stdout, '\n========\n', stderr]))
 
 
 def hack_emsdk(marker, replacement):
@@ -335,7 +335,7 @@
     print('test 32-bit error')
     emsdk_hacked = hack_emsdk('not is_os_64bit()', 'True')
     self.addCleanup(remove_file, emsdk_hacked)
-    failing_call_with_output('%s %s install latest' % (sys.executable, emsdk_hacked),
+    failing_call_with_output(f'{sys.executable} {emsdk_hacked} install latest',
                              'this tool is only provided for 64-bit OSes')
 
   def test_update_no_git(self):