Skip to content

feat(packages/py): set up Ruff linting for Python bindings - #342

Merged
Ryan-Millard merged 4 commits into
Ryan-Millard:mainfrom
Geff115:feat/python-linting-ruff-setup
May 16, 2026
Merged

feat(packages/py): set up Ruff linting for Python bindings#342
Ryan-Millard merged 4 commits into
Ryan-Millard:mainfrom
Geff115:feat/python-linting-ruff-setup

Conversation

@Geff115

@Geff115 Geff115 commented May 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Sets up Ruff as the Python linter for the Python bindings and example apps,
as requested in issue #332.

Changes

pyproject.toml

  • Added [tool.ruff] and [tool.ruff.lint] configuration targeting Python 3.10+
    with rule sets covering pycodestyle (E/W), Pyflakes (F), flake8-bugbear (B),
    pyupgrade (UP), and flake8-simplify (SIM).
  • Added ruff>=0.4.0 under [project.optional-dependencies] dev extras so
    uv sync --extra dev --no-build-isolation installs it inside the container.

img2num (shell wrapper)

  • Added lint-py command that runs uv run --extra dev ruff check packages/py example-apps
    inside the Docker dev container.
  • Usage: ./img2num lint-py

Testing

Ran ./img2num lint-py inside the dev container — Ruff resolves, scans all
Python files, and reports correctly with no tooling warnings.

Note: 7 pre-existing violations were found in packages/py and example-apps/console-py.
These are not introduced by this PR and are left for a follow-up.

Related

Closes #332

@Geff115
Geff115 requested a review from Ryan-Millard as a code owner May 2, 2026 15:20
@coderabbitai

coderabbitai Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3e2d2c7d-7184-4d97-b9b7-f604fe3ae9f5

📥 Commits

Reviewing files that changed from the base of the PR and between 3a83289 and 9b38f14.

📒 Files selected for processing (1)
  • pyproject.toml
📜 Recent review details
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build C/C++ / Build WASM (bindings/js)
  • GitHub Check: Lint & Validate Code
  • GitHub Check: Build C/C++ / Build C & C++
  • GitHub Check: Build C/C++ / Build Python
  • GitHub Check: Build Documentation Site / Build Docusaurus Site
🔇 Additional comments (1)
pyproject.toml (1)

25-43: LGTM!


Release Notes

  • Add Ruff linting configuration to pyproject.toml for Python bindings and example applications
    • Target Python 3.10+ with 88-character line length
    • Enable rule sets: pycodestyle (E/W), Pyflakes (F), flake8-bugbear (B), pyupgrade (UP), flake8-simplify (SIM)
    • Exclude third_party/, docs/, and .venv/ directories
    • Allow F401 (unused imports) in init.py files for re-exports
    • Add ruff>=0.4.0 to dev optional dependencies for installation via uv
  • Add lint-py command to img2num wrapper to run Ruff linting on Python packages and example apps
    • Usage: ./img2num lint-py (runs: uv run --extra dev ruff check packages/py example-apps)
  • CI / review tooling adjustments recommended:
    • Prefer enabling Ruff and disabling Flake8 in .coderabbit.yaml (ruff: enabled true; flake8: enabled false)
    • Ensure automated lint runs target packages/py and example-apps (or add third_party to Ruff ignore) to avoid scanning third_party
  • Notes: Command tested inside dev container; Ruff resolves and scans files. The PR adds tooling and a runnable command but does not fix the pre-existing Ruff violations found (left for follow-up). Closes #332.

Changes by Author

Author Lines Added Lines Removed
Geff115 26 0

Walkthrough

Adds Ruff linting configuration to pyproject.toml (target Python 3.10, line length 88, excludes, selected rule families, per-file ignore for **/__init__.py) and declares ruff>=0.4.0 in the dev optional-dependencies group.

Changes

Ruff Linting Setup

Layer / File(s) Summary
Ruff configuration and dev dependency
pyproject.toml
Adds [tool.ruff] and [tool.ruff.lint] settings (target py310, line-length = 88, excluded paths, selected rule families E,W,F,B,UP,SIM, per-file-ignore for **/__init__.py) and adds ruff>=0.4.0 under [project.optional-dependencies].dev.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

  • #330: Both configure Ruff-based Python linting; this PR supplies Ruff settings and a dev dependency that align with issue objectives.

Suggested labels

docs

Suggested reviewers

  • Ryan-Millard

Poem

🐇 I hopped through toml, a tidy little chore,
I nudged in Ruff so linting asks for more,
Line-length and targets set just so,
Dev deps added — now let style flow! ✨

🚥 Pre-merge checks | ✅ 7 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR addresses issue #332 objectives partially: it configures Ruff in pyproject.toml and implements a working lint command, but does not update .coderabbit.yaml as required by the linked issue. Update .coderabbit.yaml to enable Ruff and disable Flake8 in reviews.tools, and add a path_instructions entry for Python files with the specified reviewer guidance.
✅ Passed checks (7 passed)
Check name Status Explanation
Title check ✅ Passed The title is specific, follows Conventional Commits format (feat:), and clearly describes the main change: setting up Ruff linting for Python bindings.
Description check ✅ Passed The description is directly related to the changeset, providing clear details about pyproject.toml configuration, img2num wrapper command, testing, and linking to issue #332.
Out of Scope Changes check ✅ Passed All changes in the PR are scoped to the linked issue objectives: pyproject.toml Ruff configuration, optional dev dependencies, and the img2num lint-py command.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
No Ai Slop Pr Description ✅ Passed PR description is specific and detailed. It explains exactly what was changed (Ruff config, .coderabbit.yaml updates) and why (issue #332). No generic boilerplate detected.
No Strangely-Named Root Markdown Files ✅ Passed The PR does not add unconventional markdown files at root. Only three acceptable files exist: README.md, CONTRIBUTING.md, and CODE_OF_CONDUCT.md. No prohibited files were added.
Coderabbit Config Needs Update ✅ Passed .coderabbit.yaml is modified with Ruff enabled, Flake8 disabled, and path_instructions for **/*.py covering all new Python linting configuration.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@img2num`:
- Around line 192-193: Update the help text for the "lint-py" command to mention
that it also starts a container and therefore supports the image flags --img,
--dh-img, and --ghcr-img; modify the help string associated with the lint-py
command (lookup the declaration or usage of "lint-py" in the CLI help generation
code) to append or replace the current description "Lint Python bindings and
examples with Ruff." with a version that clarifies container startup and lists
the supported flags (e.g., "Lint Python bindings and examples with Ruff; starts
a container — supports --img, --dh-img, --ghcr-img").
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0ff3d752-042c-4537-bde5-08b8178e411b

📥 Commits

Reviewing files that changed from the base of the PR and between fce52c6 and 45e8f8b.

📒 Files selected for processing (2)
  • img2num
  • pyproject.toml
📜 Review details
🔇 Additional comments (2)
pyproject.toml (1)

25-46: Ruff setup looks consistent.

The target version, rule set, per-file ignore, and dev extra all line up with the new Python linting flow.

img2num (1)

120-135: lint-py wiring looks correct.

The new mode is routed through the container helper and invokes Ruff in the dev environment as intended.

Comment thread img2num Outdated
@Ryan-Millard
Ryan-Millard requested a review from Krasner May 2, 2026 15:28

@Ryan-Millard Ryan-Millard left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Hi, thanks for the great PR - this is a really nice implementation, and the improvements to the img2num dev script are a thoughtful touch.

I just have two requests:

  1. Add a reusable linting script

    Please define a reusable lint command in pyproject.toml (similar to uv run dev in console-py) so it can be run like:

    uv run lint

    This makes it easy to run inside the container, e.g.:

    ./img2num sh
    uv run lint

    Please avoid using Bash-specific scripts so it remains cross-shell compatible.

    Why?

    Because I, and other devs, like to do this:

    ryan@Ryans-PC:~/projects/Img2Num$ ./img2num sh
    [+] up 2/2
     ✔ Image ryanmillard/img2num-dev:latest Pulled                                                                                                                                          2.5s
     ✔ Container img2num-dev-1              Running                                                                                                                                         0.0s
    root@e6f62aadf4dd:/usr/src/app# uv run --extra dev ruff check packages/py example-apps
    F401 [*] `numpy` imported but unused
     --> example-apps/console-py/main.py:8:17
    ...

    Note that we enter the interactive shell and then run whatever follow-up command we needed.

  2. Remove changes to img2num

    These changes are useful, but they’re out of scope for this PR.
    Since this PR is focused on linting setup, it would be better to move the img2num changes into a separate PR to keep things focused.

    If you're interested, I’d definitely like to revisit this in a follow-up PR - especially extending the proxy pattern (like pnpm <args> so we'd have uv <args>) to support more commands.

@Geff115

Geff115 commented May 4, 2026

Copy link
Copy Markdown
Contributor Author

Hi @Ryan-Millard, I've hit a constraint I'd like your input on.

After investigating two approaches:

  • [tool.uv.scripts] in the root pyproject.toml — not supported in uv 0.5.1 (pinned in the Dockerfile), throws an unknown field error.
  • [project.scripts] in packages/py/pyproject.toml — uv skips installing entry points from workspace members into the root .venv/bin, so uv run lint still fails from the project root.

ruff itself is available in .venv/bin/ruff, so uv run --extra dev ruff check packages/py example-apps works perfectly from inside the container.

Would you prefer I bump the uv version in the Dockerfile to one that supports [tool.uv.scripts], or is there another approach you'd like me to follow?

@Ryan-Millard

Ryan-Millard commented May 4, 2026

Copy link
Copy Markdown
Owner

Hi @Ryan-Millard, I've hit a constraint I'd like your input on.

After investigating two approaches:

  • [tool.uv.scripts] in the root pyproject.toml — not supported in uv 0.5.1 (pinned in the Dockerfile), throws an unknown field error.
  • [project.scripts] in packages/py/pyproject.toml — uv skips installing entry points from workspace members into the root .venv/bin, so uv run lint still fails from the project root.

ruff itself is available in .venv/bin/ruff, so uv run --extra dev ruff check packages/py example-apps works perfectly from inside the container.

Would you prefer I bump the uv version in the Dockerfile to one that supports [tool.uv.scripts], or is there another approach you'd like me to follow?

Hi @Geff115.

Thank you for asking me about this - I had no idea that uv didn't support workspace scripts.

It looks like we have a few options:

  1. Upgrade uv to a version that supports workspace-level scripts.

    I'll need you to explain which version you're considering because I haven't found a version that supports it yet.
    This is probably the best option for this repository (it depends on how it will work, though).

  2. Stick to verbose CLI commands.

    This won't be a good option because we already use CMake and pnpm - if we add one more langauge, we'll have to remember long commands for all 4 package managers.

  3. Proxy via pnpm (or make / just).

    This would entail adding a new script in the workspace's package.json and would invoke the uv CLI command.
    This would be weird (but not unconventional) because pnpm is currently set up for only JavaScript. pnpm is very popular monorepo management tool, so this wouldn't cause major problems.

I think I need @Krasner's opinion on this, though. We need a unified monorepo management system, but it's a big decision and isn't overly necessary right now.


In the meantime, please will you share the uv version you intend to use.

I think we'll update uv in any case, so please will you try updating it and implementing the script. To save you from losing time by building the Docker container, just update uv directly from its interactive shell like this:

Open img2num-dev's interactive shell:

./img2num sh

Update uv:

uv self update

Just remember to revert back to the img2num-dev version we have on Docker Hub when you're done because your Docker environment will diverge from the current dev environment that everyone else uses when you upgrade uv.

@Geff115

Geff115 commented May 5, 2026

Copy link
Copy Markdown
Contributor Author

Hi @Ryan-Millard, I tested with uv 0.11.9 (the latest, updated via uv self update) and [tool.uv.scripts] is still an unrecognised field; it doesn't appear to be a supported feature in uv at all yet.

Given the options you outlined, it sounds like option 3 (pnpm proxy) might be the most practical path forward while uv catches up. That said, since you mentioned wanting @Krasner 's input on the monorepo management decision, I'm happy to wait for that discussion before proceeding. I don't want to implement something that gets refactored shortly after.

In the meantime, should I update this PR to simply document the uv run --extra dev ruff check packages/py example-apps command as the way to run linting, so contributors at least have a clear reference?

@Ryan-Millard

Copy link
Copy Markdown
Owner

Hi @Ryan-Millard, I tested with uv 0.11.9 (the latest, updated via uv self update) and [tool.uv.scripts] is still an unrecognised field; it doesn't appear to be a supported feature in uv at all yet.

Given the options you outlined, it sounds like option 3 (pnpm proxy) might be the most practical path forward while uv catches up. That said, since you mentioned wanting @Krasner 's input on the monorepo management decision, I'm happy to wait for that discussion before proceeding. I don't want to implement something that gets refactored shortly after.

In the meantime, should I update this PR to simply document the uv run --extra dev ruff check packages/py example-apps command as the way to run linting, so contributors at least have a clear reference?

Yes, please. That would be great.

It's very strange that uv doesn't support that - it's such an important thing for monorepos.

@Krasner

Krasner commented May 11, 2026

Copy link
Copy Markdown
Collaborator

will review as soon as possible...

@Krasner

Krasner commented May 12, 2026

Copy link
Copy Markdown
Collaborator

@Ryan-Millard @Geff115
I think the pnpm method is the cleanest. unfortunately uv doesn't accept the [tool.uv.scripts] section.
In package.json > scripts add

"lint-py": "uv run --extra dev ruff check packages/py example-apps" will enable a user to simply use:

pnpm run lint-py

"scripts": {
    "help": "node scripts/help.js",
    "format": "node scripts/format.js",
    "format:check": "node scripts/format.js --check",
    "format:cpp": "node scripts/format-cpp.js",
    "format:cpp:check": "node scripts/format-cpp.js",
    "format:js": "pnpm prettier --write . --ignore-path .prettierignore --config .prettierrc",
    "format:js:check": "pnpm prettier --check . --ignore-path .prettierignore --config .prettierrc",
    "validate-scripts": "node scripts/validate-scripts.js",
    "eslint": "eslint packages/js example-apps/react-js scripts/img2num-dev-scripts docs",
    "eslint:fix": "eslint packages/js example-apps/react-js scripts/img2num-dev-scripts docs --fix",
    "editorconfig:check": "editorconfig-checker",
    "lint-py": "uv run --extra dev ruff check packages/py example-apps"
  },
  

@Ryan-Millard

Copy link
Copy Markdown
Owner

@Krasner, after having some time to think about it, I think another monorepo management tool would be better because we already have a lot of workspace-level pnpm scripts for the entire project that are dedicated to JavaScript. By adding more scripts, we'd make the package.json files large and harder to manage. I think a separation of concerns might be slightly cleaner.

I'll open a discussion about this so we can avoid the off-topic conversation on this PR.


@Geff115 we don't actually need the long uv run --extra dev ruff check packages/py example-apps command because we can just install the dev extras with uv and run ruff normally (see below). For this, we'll just need to install it into the Dockerfile.dev image so contributors don't have to worry about it at all. I need to fix the Python dependency setup in the Dockerfile.dev, so I'll also add that.

The ruff command can be simplified

root@648a77abe1dc:/usr/src/app# uv sync --extra dev
warning: Missing version constraint (e.g., a lower bound) for `numpy`
warning: Missing version constraint (e.g., a lower bound) for `opencv-python`
Resolved 7 packages in 487ms
Prepared 2 packages in 4m 10s
Uninstalled 3 packages in 66ms
░░░░░░░░░░░░░░░░░░░░ [0/2] Installing wheels...                                                                                                                                             warning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance.
         If the cache and target directories are on different filesystems, hardlinking may not be supported.
         If this is intentional, set `export UV_LINK_MODE=copy` or use `--link-mode=copy` to suppress this warning.
Installed 2 packages in 196ms
 - console-py==0.0.0 (from file:///usr/src/app/example-apps/console-py)
 - numpy==2.2.6
 + numpy==2.4.4
 - opencv-python==4.13.0.92
 + ruff==0.15.12
root@648a77abe1dc:/usr/src/app# uv run ruff check .
E401 [*] Multiple imports on one line
 --> core/tools/embed_shaders.py:1:1
  |
1 | import sys, pathlib
  | ^^^^^^^^^^^^^^^^^^^
2 |
3 | SHADER_DIR = pathlib.Path(sys.argv[1])
  |
help: Split imports

E501 Line too long (90 > 88)
  --> docs/docs/internal/core/internal-code/fft_iterative/prerequisite-theory/fourier-series--the-bridge/python_scripts/fourier_coefficient_triangle.py:18:89
   |
17 | # Draw vector X_k
18 | ax.arrow(0, 0, a, b, head_width=0.08, head_length=0.12, fc='blue', ec='blue', linewidth=2)
   |                                                                                         ^^
19 |
20 | # Draw dashed triangle sides
   |

E501 Line too long (95 > 88)
  --> docs/docs/internal/core/internal-code/fft_iterative/prerequisite-theory/fourier-series--the-bridge/python_scripts/fourier_coefficient_triangle.py:25:89
   |
24 | # Annotations - outside the triangle
25 | ax.text(a/2, -0.05, r'$a$', fontsize=12, ha='center', va='top')                     # Real side
   |                                                                                         ^^^^^^^
26 | ax.text(a + 0.2, b/2, r'$b$', fontsize=12, ha='left', va='center')                   # Imag side
   |

E501 Line too long (96 > 88)
  --> docs/docs/internal/core/internal-code/fft_iterative/prerequisite-theory/fourier-series--the-bridge/python_scripts/fourier_coefficient_triangle.py:26:89
   |
24 | # Annotations - outside the triangle
25 | ax.text(a/2, -0.05, r'$a$', fontsize=12, ha='center', va='top')                     # Real side
26 | ax.text(a + 0.2, b/2, r'$b$', fontsize=12, ha='left', va='center')                   # Imag side
   |                                                                                         ^^^^^^^^
27 |
28 | # Phase angle arc
   |

E501 Line too long (112 > 88)
  --> docs/docs/internal/core/internal-code/fft_iterative/prerequisite-theory/fourier-series--the-bridge/python_scripts/fourier_coefficient_triangle.py:37:89
   |
35 | ax.plot(a, b, 'o', color='blue')
36 | ax.text(a + 0.15, b + 0.15, r'$X_k$', fontsize=12)
37 | ax.text(a + 0.15, b + -0.15, r'$(\text{Pythag: } |X_k| = \sqrt{a^2 + b^2})$', fontsize=10, color='blue')  # Note
   |                                                                                         ^^^^^^^^^^^^^^^^^^^^^^^^
38 |
39 | # Axes labels - moved to avoid overlap
   |

E501 Line too long (93 > 88)
  --> docs/docs/internal/core/internal-code/fft_iterative/prerequisite-theory/fourier-series--the-bridge/python_scripts/time_freq_domain.py:35:89
   |
33 | axs[0].plot(t_cont, sum_cont, 'k--', label="Sum (continuous)")
34 | axs[0].plot(t_disc_jittered, sum_disc_jittered, 'ko', label="Sum (samples, jittered)")
35 | axs[0].set_title("Time Domain – Harmonics (continuous) and Sampled Sum (discrete, jittered)")
   |                                                                                         ^^^^^
36 | axs[0].set_xlabel("Time [seconds]")
37 | axs[0].set_ylabel("Amplitude")
   |

B905 `zip()` without an explicit `strict=` parameter
  --> docs/docs/internal/core/internal-code/fft_iterative/prerequisite-theory/fourier-series--the-bridge/python_scripts/time_freq_domain.py:44:33
   |
42 | lines = []
43 | labels = []
44 | for i, (freq, amp) in enumerate(zip(freqs, amplitudes)):
   |                                 ^^^^^^^^^^^^^^^^^^^^^^
45 |     markerline, stemlines, baseline = axs[1].stem([freq], [amp], linefmt=colors[i], markerfmt=f'{colors[i]}o', basefmt=" ")
46 |     lines.append(markerline)
   |
help: Add explicit value for parameter `strict=`

E501 Line too long (123 > 88)
  --> docs/docs/internal/core/internal-code/fft_iterative/prerequisite-theory/fourier-series--the-bridge/python_scripts/time_freq_domain.py:45:89
   |
43 | labels = []
44 | for i, (freq, amp) in enumerate(zip(freqs, amplitudes)):
45 |     markerline, stemlines, baseline = axs[1].stem([freq], [amp], linefmt=colors[i], markerfmt=f'{colors[i]}o', basefmt=" ")
   |                                                                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
46 |     lines.append(markerline)
47 |     labels.append(f"Harmonic {i+1}")
   |

E501 Line too long (97 > 88)
  --> docs/docs/internal/core/internal-code/fft_iterative/prerequisite-theory/fourier-series--the-bridge/python_scripts/time_freq_domain.py:49:89
   |
47 |     labels.append(f"Harmonic {i+1}")
48 |
49 | axs[1].set_title("Frequency Domain – Peaks at Harmonic Frequencies (colored to match harmonics)")
   |                                                                                         ^^^^^^^^^
50 | axs[1].set_xlabel("Frequency [Hz]")
51 | axs[1].set_ylabel("Magnitude")
   |

E501 Line too long (99 > 88)
  --> docs/docs/internal/core/internal-code/fft_iterative/prerequisite-theory/fourier-series--the-bridge/python_scripts/time_freq_domain.py:53:89
   |
51 | axs[1].set_ylabel("Magnitude")
52 | axs[1].grid(True)
53 | axs[1].legend(lines, labels, loc='center left', bbox_to_anchor=(1, 0.5))  # legend outside the plot
   |                                                                                         ^^^^^^^^^^^
54 |
55 | plt.tight_layout()
   |

E501 Line too long (93 > 88)
  --> docs/docs/internal/core/internal-code/fft_iterative/prerequisite-theory/introduction/python_scripts/continuous-sinusoid.py:10:89
   |
 9 | # === Time vector and signal ===
10 | # Create an array 't' of evenly spaced time points from 0 up to (but not including) 1 second.
   |                                                                                         ^^^^^
11 | # The total number of points = fs * duration = 100 samples.
12 | t = np.linspace(0, duration, int(fs * duration), endpoint=False)
   |

E501 Line too long (89 > 88)
  --> docs/docs/internal/core/internal-code/fft_iterative/prerequisite-theory/introduction/python_scripts/continuous-sinusoid.py:22:89
   |
21 | # === Fourier Transform ===
22 | # Compute the Fast Fourier Transform (FFT) of the signal to get its frequency components.
   |                                                                                         ^
23 | X = np.fft.fft(x)
   |

E501 Line too long (92 > 88)
  --> docs/docs/internal/core/internal-code/fft_iterative/prerequisite-theory/introduction/python_scripts/discrete-sinusoid.py:29:89
   |
27 | num_points = min(max(1, num_points), N)
28 |
29 | # Randomly pick indices WITHOUT replacement, then sort them so points progress left-to-right
   |                                                                                         ^^^^
30 | chosen_indices = np.sort(np.random.choice(n, size=num_points, replace=False))
31 | n_down = chosen_indices
   |

E501 Line too long (103 > 88)
  --> docs/docs/internal/core/internal-code/fft_iterative/prerequisite-theory/introduction/python_scripts/discrete-sinusoid.py:35:89
   |
34 | # Add small random jitter:
35 | # x_jitter_frac: fraction of the sample interval (1/fs). 0.15 => ┬▒15% of sample interval horizontally.
   |                                                                                         ^^^^^^^^^^^^^^^
36 | # y_jitter: absolute amplitude jitter (in signal units). Keep small so the waveform is still visible.
37 | x_jitter_frac = 0.15
   |

E501 Line too long (101 > 88)
  --> docs/docs/internal/core/internal-code/fft_iterative/prerequisite-theory/introduction/python_scripts/discrete-sinusoid.py:36:89
   |
34 | # Add small random jitter:
35 | # x_jitter_frac: fraction of the sample interval (1/fs). 0.15 => ┬▒15% of sample interval horizontally.
36 | # y_jitter: absolute amplitude jitter (in signal units). Keep small so the waveform is still visible.
   |                                                                                         ^^^^^^^^^^^^^
37 | x_jitter_frac = 0.15
38 | y_jitter = 0.03
   |

E501 Line too long (90 > 88)
  --> docs/versioned_docs/version-old/reference/wasm/modules/image/fft_iterative/prerequisite-theory/fourier-series--the-bridge/python_scripts/fourier_coefficient_triangle.py:18:89
   |
17 | # Draw vector X_k
18 | ax.arrow(0, 0, a, b, head_width=0.08, head_length=0.12, fc='blue', ec='blue', linewidth=2)
   |                                                                                         ^^
19 |
20 | # Draw dashed triangle sides
   |

E501 Line too long (95 > 88)
  --> docs/versioned_docs/version-old/reference/wasm/modules/image/fft_iterative/prerequisite-theory/fourier-series--the-bridge/python_scripts/fourier_coefficient_triangle.py:25:89
   |
24 | # Annotations - outside the triangle
25 | ax.text(a/2, -0.05, r'$a$', fontsize=12, ha='center', va='top')                     # Real side
   |                                                                                         ^^^^^^^
26 | ax.text(a + 0.2, b/2, r'$b$', fontsize=12, ha='left', va='center')                   # Imag side
   |

E501 Line too long (96 > 88)
  --> docs/versioned_docs/version-old/reference/wasm/modules/image/fft_iterative/prerequisite-theory/fourier-series--the-bridge/python_scripts/fourier_coefficient_triangle.py:26:89
   |
24 | # Annotations - outside the triangle
25 | ax.text(a/2, -0.05, r'$a$', fontsize=12, ha='center', va='top')                     # Real side
26 | ax.text(a + 0.2, b/2, r'$b$', fontsize=12, ha='left', va='center')                   # Imag side
   |                                                                                         ^^^^^^^^
27 |
28 | # Phase angle arc
   |

E501 Line too long (112 > 88)
  --> docs/versioned_docs/version-old/reference/wasm/modules/image/fft_iterative/prerequisite-theory/fourier-series--the-bridge/python_scripts/fourier_coefficient_triangle.py:37:89
   |
35 | ax.plot(a, b, 'o', color='blue')
36 | ax.text(a + 0.15, b + 0.15, r'$X_k$', fontsize=12)
37 | ax.text(a + 0.15, b + -0.15, r'$(\text{Pythag: } |X_k| = \sqrt{a^2 + b^2})$', fontsize=10, color='blue')  # Note
   |                                                                                         ^^^^^^^^^^^^^^^^^^^^^^^^
38 |
39 | # Axes labels - moved to avoid overlap
   |

E501 Line too long (93 > 88)
  --> docs/versioned_docs/version-old/reference/wasm/modules/image/fft_iterative/prerequisite-theory/fourier-series--the-bridge/python_scripts/time_freq_domain.py:35:89
   |
33 | axs[0].plot(t_cont, sum_cont, 'k--', label="Sum (continuous)")
34 | axs[0].plot(t_disc_jittered, sum_disc_jittered, 'ko', label="Sum (samples, jittered)")
35 | axs[0].set_title("Time Domain – Harmonics (continuous) and Sampled Sum (discrete, jittered)")
   |                                                                                         ^^^^^
36 | axs[0].set_xlabel("Time [seconds]")
37 | axs[0].set_ylabel("Amplitude")
   |

B905 `zip()` without an explicit `strict=` parameter
  --> docs/versioned_docs/version-old/reference/wasm/modules/image/fft_iterative/prerequisite-theory/fourier-series--the-bridge/python_scripts/time_freq_domain.py:44:33
   |
42 | lines = []
43 | labels = []
44 | for i, (freq, amp) in enumerate(zip(freqs, amplitudes)):
   |                                 ^^^^^^^^^^^^^^^^^^^^^^
45 |     markerline, stemlines, baseline = axs[1].stem([freq], [amp], linefmt=colors[i], markerfmt=f'{colors[i]}o', basefmt=" ")
46 |     lines.append(markerline)
   |
help: Add explicit value for parameter `strict=`

E501 Line too long (123 > 88)
  --> docs/versioned_docs/version-old/reference/wasm/modules/image/fft_iterative/prerequisite-theory/fourier-series--the-bridge/python_scripts/time_freq_domain.py:45:89
   |
43 | labels = []
44 | for i, (freq, amp) in enumerate(zip(freqs, amplitudes)):
45 |     markerline, stemlines, baseline = axs[1].stem([freq], [amp], linefmt=colors[i], markerfmt=f'{colors[i]}o', basefmt=" ")
   |                                                                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
46 |     lines.append(markerline)
47 |     labels.append(f"Harmonic {i+1}")
   |

E501 Line too long (97 > 88)
  --> docs/versioned_docs/version-old/reference/wasm/modules/image/fft_iterative/prerequisite-theory/fourier-series--the-bridge/python_scripts/time_freq_domain.py:49:89
   |
47 |     labels.append(f"Harmonic {i+1}")
48 |
49 | axs[1].set_title("Frequency Domain – Peaks at Harmonic Frequencies (colored to match harmonics)")
   |                                                                                         ^^^^^^^^^
50 | axs[1].set_xlabel("Frequency [Hz]")
51 | axs[1].set_ylabel("Magnitude")
   |

E501 Line too long (99 > 88)
  --> docs/versioned_docs/version-old/reference/wasm/modules/image/fft_iterative/prerequisite-theory/fourier-series--the-bridge/python_scripts/time_freq_domain.py:53:89
   |
51 | axs[1].set_ylabel("Magnitude")
52 | axs[1].grid(True)
53 | axs[1].legend(lines, labels, loc='center left', bbox_to_anchor=(1, 0.5))  # legend outside the plot
   |                                                                                         ^^^^^^^^^^^
54 |
55 | plt.tight_layout()
   |

E501 Line too long (93 > 88)
  --> docs/versioned_docs/version-old/reference/wasm/modules/image/fft_iterative/prerequisite-theory/introduction/python_scripts/continuous-sinusoid.py:10:89
   |
 9 | # === Time vector and signal ===
10 | # Create an array 't' of evenly spaced time points from 0 up to (but not including) 1 second.
   |                                                                                         ^^^^^
11 | # The total number of points = fs * duration = 100 samples.
12 | t = np.linspace(0, duration, int(fs * duration), endpoint=False)
   |

E501 Line too long (89 > 88)
  --> docs/versioned_docs/version-old/reference/wasm/modules/image/fft_iterative/prerequisite-theory/introduction/python_scripts/continuous-sinusoid.py:22:89
   |
21 | # === Fourier Transform ===
22 | # Compute the Fast Fourier Transform (FFT) of the signal to get its frequency components.
   |                                                                                         ^
23 | X = np.fft.fft(x)
   |

E501 Line too long (92 > 88)
  --> docs/versioned_docs/version-old/reference/wasm/modules/image/fft_iterative/prerequisite-theory/introduction/python_scripts/discrete-sinusoid.py:29:89
   |
27 | num_points = min(max(1, num_points), N)
28 |
29 | # Randomly pick indices WITHOUT replacement, then sort them so points progress left-to-right
   |                                                                                         ^^^^
30 | chosen_indices = np.sort(np.random.choice(n, size=num_points, replace=False))
31 | n_down = chosen_indices
   |

E501 Line too long (103 > 88)
  --> docs/versioned_docs/version-old/reference/wasm/modules/image/fft_iterative/prerequisite-theory/introduction/python_scripts/discrete-sinusoid.py:35:89
   |
34 | # Add small random jitter:
35 | # x_jitter_frac: fraction of the sample interval (1/fs). 0.15 => ┬▒15% of sample interval horizontally.
   |                                                                                         ^^^^^^^^^^^^^^^
36 | # y_jitter: absolute amplitude jitter (in signal units). Keep small so the waveform is still visible.
37 | x_jitter_frac = 0.15
   |

E501 Line too long (101 > 88)
  --> docs/versioned_docs/version-old/reference/wasm/modules/image/fft_iterative/prerequisite-theory/introduction/python_scripts/discrete-sinusoid.py:36:89
   |
34 | # Add small random jitter:
35 | # x_jitter_frac: fraction of the sample interval (1/fs). 0.15 => ┬▒15% of sample interval horizontally.
36 | # y_jitter: absolute amplitude jitter (in signal units). Keep small so the waveform is still visible.
   |                                                                                         ^^^^^^^^^^^^^
37 | x_jitter_frac = 0.15
38 | y_jitter = 0.03
   |

F401 [*] `numpy` imported but unused
 --> example-apps/console-py/main.py:8:17
  |
6 | # if not preset install these with
7 | # python3 -m pip install -r requirements.txt --break-system-packages
8 | import numpy as np
  |                 ^^
9 | import cv2
  |
help: Remove unused import: `numpy`

E501 Line too long (100 > 88)
  --> example-apps/console-py/main.py:23:89
   |
21 | # bilateral filter in-place
22 |   img_bf = img2num.bilateral_filter(img, 3, 50, 0)
23 |   cv2.imwrite(os.path.join(OUTDIR, "bilateral_image.png"), cv2.cvtColor(img_bf, cv2.COLOR_RGBA2BGR))
   |                                                                                         ^^^^^^^^^^^^
24 |
25 | # kmeans
   |

E501 Line too long (101 > 88)
  --> example-apps/console-py/main.py:27:89
   |
25 | # kmeans
26 |   img_kmeans, labels = img2num.kmeans(img_bf, 16, 100, 0)
27 |   cv2.imwrite(os.path.join(OUTDIR, "kmeans_image.png"), cv2.cvtColor(img_kmeans, cv2.COLOR_RGBA2BGR))
   |                                                                                         ^^^^^^^^^^^^^
28 | # svg file
29 |   res_svg = img2num.labels_to_svg(img, labels, 100)
   |

F403 `from .api import *` used; unable to detect undefined names
 --> packages/py/img2num/__init__.py:1:1
  |
1 | from .api import *
  | ^^^^^^^^^^^^^^^^^^
  |

F401 [*] `numpy` imported but unused
 --> packages/py/img2num/api.py:1:17
  |
1 | import numpy as np
  |                 ^^
2 | import inspect
3 | import functools
  |
help: Remove unused import: `numpy`

SIM108 Use ternary operator `image = kwargs[image_arg] if image_arg in kwargs else args[idx]` instead of `if`-`else`-block
  --> packages/py/img2num/api.py:21:13
   |
19 |           @functools.wraps(fn)
20 |           def wrapper(*args, **kwargs):
21 | /             if image_arg in kwargs:
22 | |                 image = kwargs[image_arg]
23 | |             else:
24 | |                 image = args[idx]
   | |_________________________________^
25 |
26 |               if image.ndim < 2:
   |
help: Replace `if`-`else`-block with `image = kwargs[image_arg] if image_arg in kwargs else args[idx]`

E501 Line too long (91 > 88)
  --> packages/py/img2num/api.py:53:89
   |
51 | @_inject_dims("image")
52 | def bilateral_filter(image, sigma_spatial, sigma_range, color_space, *, width, height):
53 |     return _bilateral_filter(image, width, height, sigma_spatial, sigma_range, color_space)
   |                                                                                         ^^^
54 |
55 | @_inject_dims("data")
   |

E501 Line too long (103 > 88)
   --> third_party/dawn/PRESUBMIT.py:132:89
    |
130 |                 if match := reg.search(line):
131 |                     matches.append(
132 |                         f"{f.LocalPath()} ({line_num}): found non-inclusive language: {match.group(0)}"
    |                                                                                         ^^^^^^^^^^^^^^^
133 |                     )
    |

E501 Line too long (93 > 88)
   --> third_party/dawn/PRESUBMIT.py:145:89
    |
144 | def _CalculateEnumeratedEntriesAndTypes(lines):
145 |     """Returns a dictionary of enumerated entries, and a list of all the 'types' encountered.
    |                                                                                         ^^^^^
146 |
147 |     The implemented parsing is unsophisticated, and assumes a readable/well-formed .proto file.
    |

E501 Line too long (95 > 88)
   --> third_party/dawn/PRESUBMIT.py:147:89
    |
145 |     """Returns a dictionary of enumerated entries, and a list of all the 'types' encountered.
146 |
147 |     The implemented parsing is unsophisticated, and assumes a readable/well-formed .proto file.
    |                                                                                         ^^^^^^^
148 |     Things like unmatched '{}'s will cause a crash. Missing ';'s or writing something like `} message Foo {` will also
149 |     cause misbehaviour.
    |

E501 Line too long (118 > 88)
   --> third_party/dawn/PRESUBMIT.py:148:89
    |
147 |     The implemented parsing is unsophisticated, and assumes a readable/well-formed .proto file.
148 |     Things like unmatched '{}'s will cause a crash. Missing ';'s or writing something like `} message Foo {` will also
    |                                                                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
149 |     cause misbehaviour.
150 |     Constructs like this are normally bad style, so if really needed, adding support for them is left as an exercise for
    |

E501 Line too long (120 > 88)
   --> third_party/dawn/PRESUBMIT.py:150:89
    |
148 |     Things like unmatched '{}'s will cause a crash. Missing ';'s or writing something like `} message Foo {` will also
149 |     cause misbehaviour.
150 |     Constructs like this are normally bad style, so if really needed, adding support for them is left as an exercise for
    |                                                                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
151 |     the reader.
152 |     """
    |

E741 Ambiguous variable name: `l`
   --> third_party/dawn/PRESUBMIT.py:164:9
    |
162 |     oneof_scopes = set()
163 |     types = []
164 |     for l in lines:
    |         ^
165 |         l = l.strip().rstrip()
166 |         l = l.split("//", 1)[0]
    |

E741 Ambiguous variable name: `l`
   --> third_party/dawn/PRESUBMIT.py:165:9
    |
163 |     types = []
164 |     for l in lines:
165 |         l = l.strip().rstrip()
    |         ^
166 |         l = l.split("//", 1)[0]
167 |         while l:
    |

E741 Ambiguous variable name: `l`
   --> third_party/dawn/PRESUBMIT.py:166:9
    |
164 |     for l in lines:
165 |         l = l.strip().rstrip()
166 |         l = l.split("//", 1)[0]
    |         ^
167 |         while l:
168 |             if match := re.search(push_re, l):
    |

E741 Ambiguous variable name: `l`
   --> third_party/dawn/PRESUBMIT.py:174:17
    |
172 |                 prefix_str = '.'.join(prefix_stack)
173 |                 types.append(prefix_str)
174 |                 l = match.group(2)
    |                 ^
175 |                 continue
176 |             if match := re.search(reserved_re, l):
    |

E741 Ambiguous variable name: `l`
   --> third_party/dawn/PRESUBMIT.py:182:17
    |
180 |                 reserved_numbers.extend(new_numbers)
181 |                 enumerated_entries[f"{prefix_str}.reserved"] = reserved_numbers
182 |                 l = match.group(1)
    |                 ^
183 |                 continue
184 |             if match := re.search(value_re, l):
    |

E741 Ambiguous variable name: `l`
   --> third_party/dawn/PRESUBMIT.py:187:17
    |
185 |                 enumerated_entries[
186 |                     f"{prefix_str}.{match.group(1)}"] = match.group(2)
187 |                 l = match.group(2)
    |                 ^
188 |                 continue
189 |             if match := re.search(pop_re, l):
    |

E741 Ambiguous variable name: `l`
   --> third_party/dawn/PRESUBMIT.py:192:17
    |
190 |                 prefix_stack.pop()
191 |                 prefix_str = '.'.join(prefix_stack)
192 |                 l = match.group(1)
    |                 ^
193 |                 continue
194 |             l = ""
    |

E741 Ambiguous variable name: `l`
   --> third_party/dawn/PRESUBMIT.py:194:13
    |
192 |                 l = match.group(1)
193 |                 continue
194 |             l = ""
    |             ^
195 |
196 |     return enumerated_entries, types, oneof_scopes
    |

E501 Line too long (118 > 88)
   --> third_party/dawn/PRESUBMIT.py:211:89
    |
209 |             return [
210 |                 output_api.PresubmitError(
211 |                     f"Unexpectedly found more than one ir.proto in change, [{file.AbsoluteLocalPath()}, {proto_file}]"
    |                                                                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
212 |                 )
213 |             ]
    |

UP031 Use format specifiers instead of percent format
   --> third_party/dawn/PRESUBMIT.py:274:54
    |
272 |     except input_api.subprocess.CalledProcessError as e:
273 |         if input_api.is_committing:
274 |             results.append(output_api.PresubmitError('%s' % (e, )))
    |                                                      ^^^^^^^^^^^^
275 |         else:
276 |             results.append(output_api.PresubmitPromptWarning('%s' % (e, )))
    |
help: Replace with format specifiers

UP031 Use format specifiers instead of percent format
   --> third_party/dawn/PRESUBMIT.py:276:62
    |
274 |             results.append(output_api.PresubmitError('%s' % (e, )))
275 |         else:
276 |             results.append(output_api.PresubmitPromptWarning('%s' % (e, )))
    |                                                              ^^^^^^^^^^^^
277 |     return results
    |
help: Replace with format specifiers

UP031 Use format specifiers instead of percent format
   --> third_party/dawn/PRESUBMIT.py:308:54
    |
306 |     except input_api.subprocess.CalledProcessError as e:
307 |         if input_api.is_committing:
308 |             results.append(output_api.PresubmitError('%s' % (e, )))
    |                                                      ^^^^^^^^^^^^
309 |         else:
310 |             results.append(output_api.PresubmitPromptWarning('%s' % (e, )))
    |
help: Replace with format specifiers

UP031 Use format specifiers instead of percent format
   --> third_party/dawn/PRESUBMIT.py:310:62
    |
308 |             results.append(output_api.PresubmitError('%s' % (e, )))
309 |         else:
310 |             results.append(output_api.PresubmitPromptWarning('%s' % (e, )))
    |                                                              ^^^^^^^^^^^^
311 |     return results
    |
help: Replace with format specifiers

E501 Line too long (109 > 88)
   --> third_party/dawn/PRESUBMIT.py:323:89
    |
322 | def _CheckCopyrightHeaders(input_api, output_api):
323 |     """Checks that newly added files have a correct copyright year and prompts when it finds a discrepancy"""
    |                                                                                         ^^^^^^^^^^^^^^^^^^^^^
324 |     current_year = int(input_api.time.strftime('%Y'))
325 |     copyright_regex = re.compile(r'Copyright (\d{4})')
    |

E501 Line too long (121 > 88)
   --> third_party/dawn/PRESUBMIT.py:368:89
    |
366 |                     errors.append(
367 |                         output_api.PresubmitPromptWarning(
368 |                             f'{f.LocalPath()}: Copyright year is {year}, should be {current_year} as this is a new file.'
    |                                                                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
369 |                         ))
370 |                 break
    |

E401 [*] Multiple imports on one line
  --> third_party/dawn/generator/dawn_gpu_info_generator.py:29:1
   |
27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
29 | import json, os, sys
   | ^^^^^^^^^^^^^^^^^^^^
30 | from collections import namedtuple
   |
help: Split imports

F401 [*] `collections.namedtuple` imported but unused
  --> third_party/dawn/generator/dawn_gpu_info_generator.py:30:25
   |
29 | import json, os, sys
30 | from collections import namedtuple
   |                         ^^^^^^^^^^
31 |
32 | from generator_lib import Generator, run_generator, FileRender, GeneratorOutput
   |
help: Remove unused import: `collections.namedtuple`

UP032 [*] Use f-string instead of `format` call
  --> third_party/dawn/generator/dawn_gpu_info_generator.py:93:48
   |
92 |   …     # Don't allow duplicate entries
93 |   …     assert device not in self.devices, 'Architecture "{}" contained duplicate deviceID "{}"'.format(
   |  __________________________________________^
94 | |self.name.get(), device)
   | |__________________________________^
95 |# Ensure that all device IDs don't contain bits outside the mask
96 |   …     assert device_num & mask_num == device_num, 'Architecture "{}" contained deviceID "{}" which doesn\'t match the given mask of "{…
   |
help: Convert to f-string

E501 Line too long (108 > 88)
  --> third_party/dawn/generator/dawn_gpu_info_generator.py:93:89
   |
92 |             # Don't allow duplicate entries
93 |             assert device not in self.devices, 'Architecture "{}" contained duplicate deviceID "{}"'.format(
   |                                                                                         ^^^^^^^^^^^^^^^^^^^^
94 |                 self.name.get(), device)
95 |             # Ensure that all device IDs don't contain bits outside the mask
   |

UP032 [*] Use f-string instead of `format` call
  --> third_party/dawn/generator/dawn_gpu_info_generator.py:96:57
   |
94 |                   self.name.get(), device)
95 |               # Ensure that all device IDs don't contain bits outside the mask
96 |               assert device_num & mask_num == device_num, 'Architecture "{}" contained deviceID "{}" which doesn\'t match the given mas…
   |  _________________________________________________________^
97 | |                 self.name.get(), device, mask)
   | |______________________________________________^
98 |
99 |               self.devices.append(device)
   |
help: Convert to f-string

E501 Line too long (151 > 88)
  --> third_party/dawn/generator/dawn_gpu_info_generator.py:96:89
   |
94 | …
95 | …n bits outside the mask
96 | …m, 'Architecture "{}" contained deviceID "{}" which doesn\'t match the given mask of "{}"'.format(
   |                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
97 | …
   |

E501 Line too long (94 > 88)
   --> third_party/dawn/generator/dawn_gpu_info_generator.py:123:89
    |
121 |                 architecture = Architecture(arch_name, arch_data, self.mask)
122 |
123 |                 # Validate that deviceIDs are only allowed to be in one Architecture at a time
    |                                                                                         ^^^^^^
124 |                 for other_architecture in self.architectures:
125 |                     for device in architecture.devices:
    |

UP032 [*] Use f-string instead of `format` call
   --> third_party/dawn/generator/dawn_gpu_info_generator.py:126:74
    |
124 |                   for other_architecture in self.architectures:
125 |                       for device in architecture.devices:
126 |                           assert device not in other_architecture.devices, 'Architectures "{}" and "{}" both contain deviceID "{}"'.for…
    |  __________________________________________________________________________^
127 | |                             architecture.name.get(),
128 | |                             other_architecture.name.get(), device)
    | |__________________________________________________________________^
129 |
130 |                   self.architectures.append(architecture)
    |
help: Convert to f-string

E501 Line too long (137 > 88)
   --> third_party/dawn/generator/dawn_gpu_info_generator.py:126:89
    |
124 | …     for other_architecture in self.architectures:
125 | …         for device in architecture.devices:
126 | …             assert device not in other_architecture.devices, 'Architectures "{}" and "{}" both contain deviceID "{}"'.format(
    |                                                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
127 | …                 architecture.name.get(),
128 | …                 other_architecture.name.get(), device)
    |

UP032 [*] Use f-string instead of `format` call
   --> third_party/dawn/generator/dawn_gpu_info_generator.py:140:60
    |
138 |                   for device in architecture.devices:
139 |                       device_num = int(device, 0) & combined_mask
140 |                       assert device_num != other_device_num, 'DeviceID "{}" & mask "{}" conflicts with deviceId "{}" & mask "{}" in arc…
    |  ____________________________________________________________^
141 | |                         other_device, other_mask, device, self.mask,
142 | |                         architecture.name.get())
    | |________________________________________________^
143 |
144 |       def maskDeviceId(self):
    |
help: Convert to f-string

E501 Line too long (156 > 88)
   --> third_party/dawn/generator/dawn_gpu_info_generator.py:140:89
    |
138 | …
139 | …ed_mask
140 | …m, 'DeviceID "{}" & mask "{}" conflicts with deviceId "{}" & mask "{}" in architecture "{}"'.format(
    |                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
141 | …, self.mask,
142 | …
    |

E501 Line too long (91 > 88)
   --> third_party/dawn/generator/dawn_gpu_info_generator.py:172:89
    |
170 | …     # Validate that deviceIDs are unique across device sets
171 | …     for other_device_set in self.device_sets:
172 | …         # Only validate device IDs between internal and public device sets.
    |                                                                           ^^^
173 | …         if other_device_set.internal == device_set.internal:
174 | …             assert device_set.mask != other_device_set.mask, 'Vendor "{}" contained duplicate device masks "{}"'.format(
    |

UP032 [*] Use f-string instead of `format` call
   --> third_party/dawn/generator/dawn_gpu_info_generator.py:174:78
    |
172 |   …     # Only validate device IDs between internal and public device sets.
173 |   …     if other_device_set.internal == device_set.internal:
174 |   …         assert device_set.mask != other_device_set.mask, 'Vendor "{}" contained duplicate device masks "{}"'.format(
    |  ____________________________________________________________^
175 | | …             self.name.get(), device_set.mask)
    | |_______________________________________________^
176 |   …         other_device_set.validate_devices(
177 |   …             architecture.devices, device_set.mask)
    |
help: Convert to f-string

E501 Line too long (136 > 88)
   --> third_party/dawn/generator/dawn_gpu_info_generator.py:174:89
    |
172 | …     # Only validate device IDs between internal and public device sets.
173 | …     if other_device_set.internal == device_set.internal:
174 | …         assert device_set.mask != other_device_set.mask, 'Vendor "{}" contained duplicate device masks "{}"'.format(
    |                                                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
175 | …             self.name.get(), device_set.mask)
176 | …         other_device_set.validate_devices(
    |

E501 Line too long (110 > 88)
   --> third_party/dawn/generator/dawn_gpu_info_generator.py:179:89
    |
177 |                                 architecture.devices, device_set.mask)
178 |
179 |                         # Validate that architecture names are unique between internal and public device sets.
    |                                                                                         ^^^^^^^^^^^^^^^^^^^^^^
180 |                         else:
181 |                             for other_architecture in other_device_set.architectures:
    |

UP032 [*] Use f-string instead of `format` call
   --> third_party/dawn/generator/dawn_gpu_info_generator.py:184:36
    |
182 |                                   assert architecture.name.canonical_case(
183 |                                   ) != other_architecture.name.canonical_case(
184 |                                   ), '"{}" is defined as both an internal and public architecture'.format(
    |  ____________________________________^
185 | |                                     architecture.name.get())
    | |____________________________________________________________^
186 |
187 |                       if device_set.internal:
    |
help: Convert to f-string

E501 Line too long (104 > 88)
   --> third_party/dawn/generator/dawn_gpu_info_generator.py:184:89
    |
182 | …                     assert architecture.name.canonical_case(
183 | …                     ) != other_architecture.name.canonical_case(
184 | …                     ), '"{}" is defined as both an internal and public architecture'.format(
    |                                                                               ^^^^^^^^^^^^^^^^
185 | …                         architecture.name.get())
    |

E501 Line too long (91 > 88)
   --> third_party/dawn/generator/dawn_gpu_info_generator.py:207:89
    |
206 |     for (vendor_name, vendor_data) in json['vendors'].items():
207 |         # Skip vendors that have a leading underscore. Those are intended to be "comments".
    |                                                                                         ^^^
208 |         if vendor_name[0] == '_':
209 |             continue
    |

E401 [*] Multiple imports on one line
  --> third_party/dawn/generator/dawn_json_generator.py:29:1
   |
27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
29 | import json, os, sys
   | ^^^^^^^^^^^^^^^^^^^^
30 | from collections import namedtuple, defaultdict
31 | from copy import deepcopy
   |
help: Split imports

E711 Comparison to `None` should be `cond is not None`
   --> third_party/dawn/generator/dawn_json_generator.py:121:16
    |
120 |     tags = json_data.get('tags')
121 |     if tags != None:
    |                ^^^^
122 |         for tag in tags:
123 |             assert tag in allowed_tags, f'unrecognized tag "{tag}"'
    |
help: Replace with `cond is not None`

E711 Comparison to `None` should be `cond is None`
   --> third_party/dawn/generator/dawn_json_generator.py:161:24
    |
159 |             value_name = m['name']
160 |             tags = validate_and_get_tags(m)
161 |             if tags == None:
    |                        ^^^^
162 |                 tags = []
    |
help: Replace with `cond is None`

E501 Line too long (91 > 88)
   --> third_party/dawn/generator/dawn_json_generator.py:182:89
    |
181 |             if 'deprecated' not in tags:
182 |                 # Emscripten implements some Dawn extensions, and some upstream things that
    |                                                                                         ^^^
183 |                 # aren't in Dawn yet.
184 |                 if 'emscripten' in tags and 'dawn' not in tags:
    |

E711 Comparison to `None` should be `cond is None`
   --> third_party/dawn/generator/dawn_json_generator.py:193:29
    |
191 |             if value_name == "undefined":
192 |                 self.hasUndefined = True
193 |             if lastValue == None:
    |                             ^^^^
194 |                 self.startValue = value
195 |             elif value != lastValue + 1:
    |
help: Replace with `cond is None`

E501 Line too long (98 > 88)
   --> third_party/dawn/generator/dawn_json_generator.py:201:89
    |
199 |                 EnumValue(Name(value_name), value, m.get('valid', True), m))
200 |
201 |         # Assert that all values except those with "enum_value_conflict": true are unique in enums
    |                                                                                         ^^^^^^^^^^
202 |         all_values = set()
203 |         for value in self.values:
    |

SIM102 Use a single `if` statement instead of nested `if` statements
   --> third_party/dawn/generator/dawn_json_generator.py:204:13
    |
202 |           all_values = set()
203 |           for value in self.values:
204 | /             if value.value in all_values:
205 | |                 #TODO(42241174) remove this condition once wgpu refactoring is complete.
206 | |                 if not value.json_data.get('enum_value_conflict', False):
    | |_________________________________________________________________________^
207 |                       raise Exception(
208 |                           "Duplicate value {} for '{}' in enum '{}'".format(
    |
help: Combine `if` statements using `and`

UP032 [*] Use f-string instead of `format` call
   --> third_party/dawn/generator/dawn_json_generator.py:208:25
    |
206 |                   if not value.json_data.get('enum_value_conflict', False):
207 |                       raise Exception(
208 | /                         "Duplicate value {} for '{}' in enum '{}'".format(
209 | |                             hex(value.value), value.name.get(), name))
    | |_____________________________________________________________________^
210 |               all_values.add(value.value)
211 |           self.is_wire_transparent = True
    |
help: Convert to f-string

E501 Line too long (99 > 88)
   --> third_party/dawn/generator/dawn_json_generator.py:291:89
    |
289 |         self.array_element_optional = array_element_optional
290 |         if array_element_optional:
291 |             assert annotation == 'const*', 'array_element_optional can only be used on array types'
    |                                                                                         ^^^^^^^^^^^
292 |         self.is_return_value = is_return_value
293 |         self.handle_type = None
    |

UP039 [*] Unnecessary parentheses after class definition
   --> third_party/dawn/generator/dawn_json_generator.py:320:13
    |
320 | class Method():
    |             ^^
321 |
322 |     def __init__(self, name, returns, arguments, autolock, json_data):
    |
help: Remove parentheses

UP039 [*] Unnecessary parentheses after class definition
   --> third_party/dawn/generator/dawn_json_generator.py:445:25
    |
445 | class ConstantDefinition():
    |                         ^^
446 |
447 |     def __init__(self, is_enabled, name, json_data):
    |
help: Remove parentheses

UP039 [*] Unnecessary parentheses after class definition
   --> third_party/dawn/generator/dawn_json_generator.py:455:26
    |
455 | class FunctionDeclaration():
    |                          ^^
456 |
457 |     def __init__(self, is_enabled, name, json_data, no_cpp=False):
    |
help: Remove parentheses

B905 `zip()` without an explicit `strict=` parameter
   --> third_party/dawn/generator/dawn_json_generator.py:497:24
    |
495 |         members_by_name[member.name.canonical_case()] = member
496 |
497 |     for (member, m) in zip(members, json_data):
    |                        ^^^^^^^^^^^^^^^^^^^^^^^
498 |         if member.annotation != 'value':
499 |             if not 'length' in m:
    |
help: Add explicit value for parameter `strict=`

E713 [*] Test for membership should be `not in`
   --> third_party/dawn/generator/dawn_json_generator.py:499:20
    |
497 |     for (member, m) in zip(members, json_data):
498 |         if member.annotation != 'value':
499 |             if not 'length' in m:
    |                    ^^^^^^^^^^^^^
500 |                 if member.type.category != 'object':
501 |                     member.length = "constant"
    |
help: Convert to `not in`

B011 Do not `assert False` (`python -O` removes these calls), raise `AssertionError()`
   --> third_party/dawn/generator/dawn_json_generator.py:504:28
    |
502 |                     member.constant_length = 1
503 |                 else:
504 |                     assert False
    |                            ^^^^^
505 |             elif isinstance(m['length'], int):
506 |                 assert m['length'] > 0
    |
help: Replace `assert False`

UP034 [*] Avoid extraneous parentheses
   --> third_party/dawn/generator/dawn_json_generator.py:545:16
    |
543 |         types[root] for root in struct.json_data.get('chain roots', [])
544 |     ]
545 |     assert all((root.category == 'structure' for root in struct.chain_roots))
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
help: Remove extraneous parentheses

E721 Use `is` and `is not` for type comparisons, or `isinstance()` for isinstance checks
   --> third_party/dawn/generator/dawn_json_generator.py:566:12
    |
564 |     assert returns != 'void', '"returns": "void" should be omitted instead'
565 |     if returns:
566 |         if type(returns) == str:
    |            ^^^^^^^^^^^^^^^^^^^^
567 |             returns = {'type': returns}
568 |         function.returns = AnnotatedTypedMember(
    |

SIM102 Use a single `if` statement instead of nested `if` statements
   --> third_party/dawn/generator/dawn_json_generator.py:645:17
    |
643 |           for method in object.methods:
644 |               for arg in method.arguments:
645 | /                 if arg.type.category == 'object':
646 | |                     if arg.optional:
    | |____________________________________^
647 |                           max_dependent_depth = max(max_dependent_depth,
648 |                                                     compute_depth(arg.type) + 1)
    |
help: Combine `if` statements using `and`

E731 Do not assign a `lambda` expression, use a `def`
   --> third_party/dawn/generator/dawn_json_generator.py:667:5
    |
666 |   def parse_json(json, enabled_tags, disabled_tags=None):
667 | /     is_enabled = lambda json_data: item_is_enabled(
668 | |         enabled_tags, json_data) and not item_is_disabled(
669 | |             disabled_tags, json_data)
    | |_____________________________________^
670 |       category_to_parser = {
671 |           'bitmask': BitmaskType,
    |
help: Rewrite `is_enabled` as a `def`

SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
   --> third_party/dawn/generator/dawn_json_generator.py:687:9
    |
686 |     by_category = {}
687 |     for name in category_to_parser.keys():
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
688 |         by_category[name] = []
    |
help: Remove `.keys()`

SIM118 [*] Use `key in dict` instead of `key in dict.keys()`
   --> third_party/dawn/generator/dawn_json_generator.py:723:9
    |
722 |     # Sort everything by name
723 |     for category in by_category.keys():
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
724 |         by_category[category] = sorted(by_category[category],
725 |                                        key=lambda typ: typ.name)
    |
help: Remove `.keys()`

E501 Line too long (96 > 88)
   --> third_party/dawn/generator/dawn_json_generator.py:774:89
    |
772 |     # Generate commands from object methods
773 |     for api_object in wire_params['by_category']['object']:
774 |         # Reference counting functions are generated separately, so label them as "handwritten".
    |                                                                                         ^^^^^^^^
775 |         wire_json['special items']['client_handwritten_commands'] += [
776 |             api_object.name.CamelCase() + 'AddRef',
    |

E501 Line too long (89 > 88)
   --> third_party/dawn/generator/dawn_json_generator.py:786:89
    |
784 |             # Only object return values, status or void are supported:
785 |             #
786 |             #- "void" is not a return value so commands can just be pushed to the server.
    |                                                                                         ^
787 |             # - objects use the wire's "promise pipelining" and will be sent associated with the
788 |             #   WireHandle provided by the client.
    |

E501 Line too long (96 > 88)
   --> third_party/dawn/generator/dawn_json_generator.py:787:89
    |
785 |             #
786 |             #- "void" is not a return value so commands can just be pushed to the server.
787 |             # - objects use the wire's "promise pipelining" and will be sent associated with the
    |                                                                                         ^^^^^^^^
788 |             #   WireHandle provided by the client.
789 |             # - "status" is used to synchronously return validation errors so the server checks that
    |

E501 Line too long (100 > 88)
   --> third_party/dawn/generator/dawn_json_generator.py:789:89
    |
787 |             # - objects use the wire's "promise pipelining" and will be sent associated with the
788 |             #   WireHandle provided by the client.
789 |             # - "status" is used to synchronously return validation errors so the server checks that
    |                                                                                         ^^^^^^^^^^^^
790 |             #   they are always a success.
791 |             #
    |

E711 Comparison to `None` should be `cond is None`
   --> third_party/dawn/generator/dawn_json_generator.py:796:41
    |
794 |             is_status = method.returns and method.returns.type.name.canonical_case(
795 |             ) == 'status'
796 |             is_void = method.returns == None
    |                                         ^^^^
797 |             if not (is_object or is_status or is_void):
798 |                 assert command_suffix in (
    |
help: Replace with `cond is None`

E501 Line too long (94 > 88)
   --> third_party/dawn/generator/dawn_json_generator.py:829:89
    |
827 |             commands.append(command)
828 |
829 |     # Generate commands from structure methods. Notes that currently this is only FreeMembers.
    |                                                                                         ^^^^^^
830 |     for api_struct in wire_params['by_category']['structure']:
831 |         wire_json['special items']['client_handwritten_commands'] += [
    |

E501 Line too long (98 > 88)
   --> third_party/dawn/generator/dawn_json_generator.py:880:89
    |
879 |     def mark_n2k(typ):
880 |         # Only proceed if it's a structure and hasn't been marked yet to avoid infinite recursion.
    |                                                                                         ^^^^^^^^^^
881 |         if isinstance(typ, StructureType) and not typ.needs_n2k:
882 |             typ.needs_n2k = True
    |

E501 Line too long (98 > 88)
   --> third_party/dawn/generator/dawn_json_generator.py:888:89
    |
887 |     def mark_k2n(typ):
888 |         # Only proceed if it's a structure and hasn't been marked yet to avoid infinite recursion.
    |                                                                                         ^^^^^^^^^^
889 |         if isinstance(typ, StructureType) and not typ.needs_k2n:
890 |             typ.needs_k2n = True
    |

E501 Line too long (94 > 88)
   --> third_party/dawn/generator/dawn_json_generator.py:960:89
    |
958 |                 for callback_info_member in member.type.members:
959 |                     if callback_info_member.type.category == 'callback function':
960 |                         # We give the callback function a new name based on the callback info.
    |                                                                                         ^^^^^^
961 |                         name = member.name.get().removesuffix(' info')
962 |                         function_member = deepcopy(callback_info_member)
    |

E501 Line too long (99 > 88)
   --> third_party/dawn/generator/dawn_json_generator.py:973:89
    |
971 |                 continue
972 |
973 |             # userdata parameter omitted because Kotlin clients can achieve the same with closures.
    |                                                                                         ^^^^^^^^^^^
974 |             if member.name.get() == 'userdata':
975 |                 continue
    |

E501 Line too long (95 > 88)
   --> third_party/dawn/generator/dawn_json_generator.py:977:89
    |
975 |                 continue
976 |
977 |             # Dawn sometimes uses 'annotation = *' for output parameters, for example to return
    |                                                                                         ^^^^^^^
978 |             # arrays. We convert the return type and strip out the parameters.
979 |             if member.annotation == '*' and member.length == 'constant':
    |

E501 Line too long (91 > 88)
   --> third_party/dawn/generator/dawn_json_generator.py:993:89
    |
991 |             yield member
992 |
993 |     # Calculate if we should, and can, provide a Kotlin default value for a given argument.
    |                                                                                         ^^^
994 |     # This will affect its order in the method parameter and structure field lists.
995 |     def kotlin_default(arg):
    |

E501 Line too long (99 > 88)
   --> third_party/dawn/generator/dawn_json_generator.py:996:89
    |
994 |     # This will affect its order in the method parameter and structure field lists.
995 |     def kotlin_default(arg):
996 |         # Optional and non-optional container parameters are defaulted to empty containers to match
    |                                                                                         ^^^^^^^^^^^
997 |         # the behavior of the JavaScript API.
998 |         if arg.length and arg.length != 'constant' and arg.type.name.get(
    |

E501 Line too long (97 > 88)
    --> third_party/dawn/generator/dawn_json_generator.py:1011:89
     |
1009 |             return 'null'
1010 |
1011 |         # Non-optional structures are defaulted to a defaulted structure if we can construct one.
     |                                                                                         ^^^^^^^^^
1012 |         # This is to match the behavior of the JavaScript API which lets clients pass undefined
1013 |         # structure values even for non-optional fields.
     |

E501 Line too long (95 > 88)
    --> third_party/dawn/generator/dawn_json_generator.py:1012:89
     |
1011 |         # Non-optional structures are defaulted to a defaulted structure if we can construct one.
1012 |         # This is to match the behavior of the JavaScript API which lets clients pass undefined
     |                                                                                         ^^^^^^^
1013 |         # structure values even for non-optional fields.
1014 |         if arg.type.category in [
     |

E501 Line too long (95 > 88)
    --> third_party/dawn/generator/dawn_json_generator.py:1020:89
     |
1018 |                 for member in arg.type.members):
1019 |             constructor_args = []
1020 |             # default_value = zero is a special defaulting variation from C we have to emulate.
     |                                                                                         ^^^^^^^
1021 |             if arg.default_value == 'zero':
1022 |                 constructor_args = [
     |

E501 Line too long (117 > 88)
    --> third_party/dawn/generator/dawn_json_generator.py:1023:89
     |
1021 |             if arg.default_value == 'zero':
1022 |                 constructor_args = [
1023 |                     f"{member.name.camelCase()} = {member.type.name.CamelCase()}.{as_ktName(value.name.CamelCase())}"
     |                                                                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1024 |                     for member in kotlin_record_members(arg.type.members)
1025 |                     if member.type.category in ['bitmask', 'enum']
     |

E501 Line too long (98 > 88)
    --> third_party/dawn/generator/dawn_json_generator.py:1030:89
     |
1028 |             return f"{kotlin_name(arg.type)}({', '.join(constructor_args)})"
1029 |
1030 |         # For bitmasks/enums we insert the full type of the default. No default value doesn't mean
     |                                                                                         ^^^^^^^^^^
1031 |         # no default in the bindings, because it should match the bitmask/enum labeled 'undefined'.
1032 |         if arg.type.category in ['bitmask', 'enum']:
     |

E501 Line too long (99 > 88)
    --> third_party/dawn/generator/dawn_json_generator.py:1031:89
     |
1030 |         # For bitmasks/enums we insert the full type of the default. No default value doesn't mean
1031 |         # no default in the bindings, because it should match the bitmask/enum labeled 'undefined'.
     |                                                                                         ^^^^^^^^^^^
1032 |         if arg.type.category in ['bitmask', 'enum']:
1033 |             for value in arg.type.values:
     |

E501 Line too long (93 > 88)
    --> third_party/dawn/generator/dawn_json_generator.py:1035:89
     |
1033 |             for value in arg.type.values:
1034 |                 if value.name.name == (arg.default_value or 'undefined'):
1035 |                     return f"{arg.type.name.CamelCase()}.{as_ktName(value.name.CamelCase())}"
     |                                                                                         ^^^^^
1036 |             return arg.default_value
     |

E501 Line too long (91 > 88)
    --> third_party/dawn/generator/dawn_json_generator.py:1043:89
     |
1042 |         if arg.type.category == 'native':
1043 |             # Is this a Dawn named constant that can be matched with the global definition?
     |                                                                                         ^^^
1044 |             constant = find_by_name(by_category["constant"], arg.default_value)
1045 |             if constant:
     |

UP031 Use format specifiers instead of percent format
    --> third_party/dawn/generator/dawn_json_generator.py:1050:24
     |
1048 |             # Convert double/floats to the Kotlin format.
1049 |             if arg.type.name.get() in ['double', 'float']:
1050 |                 return "%.1ff" % float(arg.default_value.rstrip('fF'))
     |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1051 |
1052 |             # Java doesn't have unsigned 32 bit / 64 bit variables so the cleanest workaround is
     |
help: Replace with format specifiers

E501 Line too long (96 > 88)
    --> third_party/dawn/generator/dawn_json_generator.py:1052:89
     |
1050 |                 return "%.1ff" % float(arg.default_value.rstrip('fF'))
1051 |
1052 |             # Java doesn't have unsigned 32 bit / 64 bit variables so the cleanest workaround is
     |                                                                                         ^^^^^^^^
1053 |             # to insert the bitwise equivalent of a signed number.
1054 |             if arg.type.name.get() in ['int', 'int32_t', 'uint32_t'
     |

E501 Line too long (96 > 88)
    --> third_party/dawn/generator/dawn_json_generator.py:1062:89
     |
1060 |                 return '-1'
1061 |
1062 |             # In all remaining cases the default as specified in dawn.json will work verbatim in
     |                                                                                         ^^^^^^^^
1063 |             # Kotlin.
1064 |             return arg.default_value
     |

E501 Line too long (92 > 88)
    --> third_party/dawn/generator/dawn_json_generator.py:1067:89
     |
1066 |         unreachable_code(
1067 |             f"no logic to default '{arg.type.name.get()}' in category '{arg.type.category}'"
     |                                                                                         ^^^^
1068 |         )
1069 |         return None
     |

E501 Line too long (100 > 88)
    --> third_party/dawn/generator/dawn_json_generator.py:1072:89
     |
1071 |     def kotlin_name(type):
1072 |         return f"{'GPU' if type.category in ('object', 'structure') else ''}{type.name.CamelCase()}"
     |                                                                                         ^^^^^^^^^^^^
1073 |
1074 |     def kotlin_return(method):
     |

E711 Comparison to `None` should be `cond is None`
    --> third_party/dawn/generator/dawn_json_generator.py:1080:40
     |
1078 |                 ) == 'size_t':
1079 |                     unreachable_code("Returning containers is not supported")
1080 |                 if ((method.returns == None
     |                                        ^^^^
1081 |                      or method.returns.type.name.get() == 'status')
1082 |                         and argument.type.category == 'structure'):
     |
help: Replace with `cond is None`

E501 Line too long (92 > 88)
    --> third_party/dawn/generator/dawn_json_generator.py:1088:89
     |
1086 |         if (method.returns
1087 |                 and method.returns.type.name.canonical_case() == 'status'):
1088 |             # This is a function like GPUBuffer.readMappedRange(). Its C return is a status,
     |                                                                                         ^^^^
1089 |             # but it has no "out" parameters. The idiomatic Kotlin function
1090 |             # should return Unit and throw an exception on failure.
     |

E501 Line too long (100 > 88)
    --> third_party/dawn/generator/dawn_json_generator.py:1094:89
     |
1093 |         # If the function should return an omitted structure, we return nothing instead.
1094 |         if method.returns and method.returns.type.category == 'structure' and not include_structure(
     |                                                                                         ^^^^^^^^^^^^
1095 |                 method.returns.type):
1096 |             return None
     |

E501 Line too long (96 > 88)
    --> third_party/dawn/generator/dawn_json_generator.py:1099:89
     |
1098 |         # Return values are not treated as optional to keep the Kotlin API simple.
1099 |         # Methods are expected to return an object if declared. If they can't, dawn may raise an
     |                                                                                         ^^^^^^^^
1100 |         # error (converted to a Kotlin exception); otherwise JNI will throw NullPointerException.
1101 |         # In either case the optional type is redundant.
     |

E501 Line too long (97 > 88)
    --> third_party/dawn/generator/dawn_json_generator.py:1100:89
     |
1098 |         # Return values are not treated as optional to keep the Kotlin API simple.
1099 |         # Methods are expected to return an object if declared. If they can't, dawn may raise an
1100 |         # error (converted to a Kotlin exception); otherwise JNI will throw NullPointerException.
     |                                                                                         ^^^^^^^^^
1101 |         # In either case the optional type is redundant.
1102 |         return AnnotatedTypedMember(
     |

E501 Line too long (89 > 88)
    --> third_party/dawn/generator/dawn_json_generator.py:1158:89
     |
1156 |         return f"{kt_file_path}/{kotlin_name(type)}"
1157 |
1158 |     # A structure may need to know which other structures listed it as a chain root, e.g.
     |                                                                                         ^
1159 |     # to know whether to mark the generated class 'open'.
1160 |     chain_children = defaultdict(list)
     |

E701 Multiple statements on one line (colon)
    --> third_party/dawn/generator/dawn_json_generator.py:1246:35
     |
1245 | def as_jsEnumValue(value):
1246 |     if 'jsrepr' in value.json_data: return value.json_data['jsrepr']
     |                                   ^
1247 |     return "'" + value.name.js_enum_case() + "'"
     |

E501 Line too long (89 > 88)
    --> third_party/dawn/generator/dawn_json_generator.py:1254:89
     |
1254 | # Returns a single character wasm type (v/p/i/j/f/d) if valid, a "(longer string)" if not
     |                                                                                         ^
1255 | def as_wasmType(x):
1256 |     if x is None:
     |

B011 Do not `assert False` (`python -O` removes these calls), raise `AssertionError()`
    --> third_party/dawn/generator/dawn_json_generator.py:1279:20
     |
1277 |             return f'({x.name.name})'  # Invalid
1278 |         else:
1279 |             assert False, 'Type -> ' + x.category
     |                    ^^^^^
     |
help: Replace `assert False`

UP032 [*] Use f-string instead of `format` call
    --> third_party/dawn/generator/dawn_json_generator.py:1287:20
     |
1285 |     if annotation == 'value':
1286 |         if typ.category == 'object':
1287 |             return '{}::Acquire({})'.format(as_cppType(typ.name), arg)
     |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1288 |         elif typ.category == 'structure':
1289 |             converted_members = [
     |
help: Convert to f-string

UP032 [*] Use f-string instead of `format` call
    --> third_party/dawn/generator/dawn_json_generator.py:1292:21
     |
1290 |                 convert_cType_to_cppType(
1291 |                     member.type, member.annotation,
1292 |                     '{}.{}'.format(arg, as_varName(member.name)), indent + 1)
     |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1293 |                 for member in typ.members
1294 |             ]
     |
help: Convert to f-string

UP032 [*] Use f-string instead of `format` call
    --> third_party/dawn/generator/dawn_json_generator.py:1301:20
     |
1299 |             return as_cppType(typ.name) + ' {\n' + converted_members + '\n}'
1300 |         elif typ.category == 'function pointer':
1301 |             return 'reinterpret_cast<{}>({})'.format(as_cppType(typ.name), arg)
     |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1302 |         else:
1303 |             return 'static_cast<{}>({})'.format(as_cppType(typ.name), arg)
     |
help: Convert to f-string

UP032 [*] Use f-string instead of `format` call
    --> third_party/dawn/generator/dawn_json_generator.py:1303:20
     |
1301 |             return 'reinterpret_cast<{}>({})'.format(as_cppType(typ.name), arg)
1302 |         else:
1303 |             return 'static_cast<{}>({})'.format(as_cppType(typ.name), arg)
     |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1304 |     else:
1305 |         return 'reinterpret_cast<{} {}>({})'.format(as_cppType(typ.name),
     |
help: Convert to f-string

UP032 [*] Use f-string instead of `format` call
    --> third_party/dawn/generator/dawn_json_generator.py:1305:16
     |
1303 |               return 'static_cast<{}>({})'.format(as_cppType(typ.name), arg)
1304 |       else:
1305 |           return 'reinterpret_cast<{} {}>({})'.format(as_cppType(typ.name),
     |  ________________^
1306 | |                                                     annotation, arg)
     | |____________________________________________________________________^
     |
help: Convert to f-string

E701 Multiple statements on one line (colon)
    --> third_party/dawn/generator/dawn_json_generator.py:1335:20
     |
1333 | def item_is_enabled(enabled_tags, json_data):
1334 |     tags = validate_and_get_tags(json_data)
1335 |     if tags is None: return True
     |                    ^
1336 |     return any(tag in enabled_tags for tag in tags)
     |

E701 Multiple statements on one line (colon)
    --> third_party/dawn/generator/dawn_json_generator.py:1340:29
     |
1339 | def item_is_disabled(disabled_tags, json_data):
1340 |     if disabled_tags is None: return False
     |                             ^
1341 |     tags = validate_and_get_tags(json_data)
1342 |     if tags is None: return False
     |

E701 Multiple statements on one line (colon)
    --> third_party/dawn/generator/dawn_json_generator.py:1342:20
     |
1340 |     if disabled_tags is None: return False
1341 |     tags = validate_and_get_tags(json_data)
1342 |     if tags is None: return False
     |                    ^
1343 |
1344 |     return any(tag in disabled_tags for tag in tags)
     |

B011 Do not `assert False` (`python -O` removes these calls), raise `AssertionError()`
    --> third_party/dawn/generator/dawn_json_generator.py:1398:16
     |
1396 |         return []
1397 |     else:
1398 |         assert False, "c_methods only valid on objects and structure"
     |                ^^^^^
     |
help: Replace `assert False`

B011 Do not `assert False` (`python -O` removes these calls), raise `AssertionError()`
    --> third_party/dawn/generator/dawn_json_generator.py:1454:12
     |
1453 | def unreachable_code(msg="unreachable_code"):
1454 |     assert False, msg
     |            ^^^^^
     |
help: Replace `assert False`

E711 Comparison to `None` should be `cond is not None`
    --> third_party/dawn/generator/dawn_json_generator.py:1483:25
     |
1481 |     def as_cProc(type_name, method_name):
1482 |         c_proc = c_prefix + 'Proc'
1483 |         if type_name != None:
     |                         ^^^^
1484 |             assert not type_name.native
1485 |             c_proc += type_name.CamelCase()
     |
help: Replace with `cond is not None`

E501 Line too long (127 > 88)
    --> third_party/dawn/generator/dawn_json_generator.py:1493:89
     |
1491 |             'Name': lambda name: Name(name),
1492 |             'as_nullability_annotated_cType': \
1493 |                 lambda arg: 'void' if arg is None else annotate(as_cTypeEnumSpecialCase(arg.type), arg, with_nullability=True),
     |                                                                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1494 |             'as_annotated_cType': \
1495 |                 lambda arg: 'void' if arg is None else annotate(as_cTypeEnumSpecialCase(arg.type), arg),
     |

E501 Line too long (104 > 88)
    --> third_party/dawn/generator/dawn_json_generator.py:1495:89
     |
1493 | …         lambda arg: 'void' if arg is None else annotate(as_cTypeEnumSpecialCase(arg.type), arg, with_nullability=True),
1494 | …     'as_annotated_cType': \
1495 | …         lambda arg: 'void' if arg is None else annotate(as_cTypeEnumSpecialCase(arg.type), arg),
     |                                                                                   ^^^^^^^^^^^^^^^^
1496 | …     'as_annotated_cppType': \
1497 | …         lambda arg, make_const_member=False: 'void' if arg is None else annotate(as_cppType(arg.type.name), arg, make_const_member…
     |

E501 Line too long (158 > 88)
    --> third_party/dawn/generator/dawn_json_generator.py:1497:89
     |
1495 | …otate(as_cTypeEnumSpecialCase(arg.type), arg),
1496 | …
1497 | …' if arg is None else annotate(as_cppType(arg.type.name), arg, make_const_member=make_const_member),
     |                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1498 | …
1499 | …
     |

E711 Comparison to `None` should be `cond is not None`
    --> third_party/dawn/generator/dawn_json_generator.py:2042:30
     |
2040 |     def get_dependencies(self, args):
2041 |         deps = [os.path.abspath(args.dawn_json)]
2042 |         if args.wire_json != None:
     |                              ^^^^
2043 |             deps += [os.path.abspath(args.wire_json)]
2044 |         if args.kotlin_json != None:
     |
help: Replace with `cond is not None`

E711 Comparison to `None` should be `cond is not None`
    --> third_party/dawn/generator/dawn_json_generator.py:2044:32
     |
2042 |         if args.wire_json != None:
2043 |             deps += [os.path.abspath(args.wire_json)]
2044 |         if args.kotlin_json != None:
     |                                ^^^^
2045 |             deps += [os.path.abspath(args.kotlin_json)]
2046 |         return deps
     |
help: Replace with `cond is not None`

E401 [*] Multiple imports on one line
  --> third_party/dawn/generator/dawn_version_generator.py:29:1
   |
27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
29 | import os, subprocess, sys, shutil
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
30 |
31 | from generator_lib import Generator, run_generator, FileRender, GeneratorOutput
   |
help: Split imports

E501 Line too long (96 > 88)
  --> third_party/dawn/generator/dawn_version_generator.py:52:89
   |
50 |     except Exception:
51 |         return ""
52 |     # No hash was available (possibly) because the directory was not a git checkout. Dawn should
   |                                                                                         ^^^^^^^^
53 |     # explicitly handle its absenece and disable features relying on the hash, i.e. caching.
54 |     return ""
   |

E501 Line too long (92 > 88)
  --> third_party/dawn/generator/dawn_version_generator.py:53:89
   |
51 |         return ""
52 |     # No hash was available (possibly) because the directory was not a git checkout. Dawn should
53 |     # explicitly handle its absenece and disable features relying on the hash, i.e. caching.
   |                                                                                         ^^^^
54 |     return ""
   |

E501 Line too long (100 > 88)
  --> third_party/dawn/generator/dawn_version_generator.py:93:89
   |
91 |                             result.stdout.decode("utf-8").strip())
92 |
93 |     # Check a packed-refs file exists. If so, we need to potentially unpack and include it as a dep.
   |                                                                                         ^^^^^^^^^^^^
94 |     packed = os.path.join(dawn_dir, ".git", "packed-refs")
95 |     if os.path.exists(packed) and unpack_git_ref(packed, resolved):
   |

E501 Line too long (100 > 88)
   --> third_party/dawn/generator/dawn_version_generator.py:136:89
    |
134 |     def get_description(self):
135 |         return (
136 |             "Generates version dependent Dawn code. Currently regenerated dependent on the version "
    |                                                                                         ^^^^^^^^^^^^
137 |             "header (if available), otherwise tries to use git hash.")
    |

E401 [*] Multiple imports on one line
  --> third_party/dawn/generator/extract_json.py:28:1
   |
26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | import os, sys, json
   | ^^^^^^^^^^^^^^^^^^^^
29 |
30 | if __name__ == "__main__":
   |
help: Split imports

UP015 [*] Unnecessary mode argument
  --> third_party/dawn/generator/extract_json.py:50:36
   |
48 |         # Skip writing to the file if it already has the correct content.
49 |         try:
50 |             with open(output_file, 'r') as outfile:
   |                                    ^^^
51 |                 if outfile.read() == content:
52 |                     continue
   |
help: Remove mode argument

UP024 [*] Replace aliased errors with `OSError`
  --> third_party/dawn/generator/extract_json.py:53:16
   |
51 |                 if outfile.read() == content:
52 |                     continue
53 |         except (OSError, EnvironmentError):
   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^
54 |             pass
   |
help: Replace with builtin `OSError`

E401 [*] Multiple imports on one line
  --> third_party/dawn/generator/generator_lib.py:51:1
   |
49 | """
50 |
51 | import argparse, json, os, re, sys
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
52 | from collections import namedtuple
   |
help: Split imports

E402 Module level import not at top of file
   --> third_party/dawn/generator/generator_lib.py:129:1
    |
127 |     pass
128 |
129 | import jinja2
    | ^^^^^^^^^^^^^
    |

E713 [*] Test for membership should be `not in`
   --> third_party/dawn/generator/generator_lib.py:146:16
    |
145 |     def get_source(self, environment, template):
146 |         if not template in self.allow_list:
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^
147 |             raise jinja2.TemplateNotFound(template)
    |
help: Convert to `not in`

E711 Comparison to `None` should be `cond is not None`
   --> third_party/dawn/generator/generator_lib.py:345:24
    |
344 |     # Output a list of all dependencies for CMake or the tarball for GN/Ninja.
345 |     if args.depfile != None or args.print_cmake_dependencies:
    |                        ^^^^
346 |         dependencies = generator.get_dependencies(args)
347 |         dependencies += [
    |
help: Replace with `cond is not None`

E711 Comparison to `None` should be `cond is not None`
   --> third_party/dawn/generator/generator_lib.py:357:28
    |
355 |         dependencies += _compute_python_dependencies(args.root_dir)
356 |
357 |         if args.depfile != None:
    |                            ^^^^
358 |             with open(args.depfile, 'w') as f:
359 |                 f.write(args.output_json_tarball + ": " +
    |
help: Replace with `cond is not None`

E711 Comparison to `None` should be `cond is not None`
   --> third_party/dawn/generator/generator_lib.py:368:38
    |
366 |     # The caller wants to assert that the outputs are what it expects.
367 |     # Load the file and compare with our renders.
368 |     if args.expected_outputs_file != None:
    |                                      ^^^^
369 |         with open(args.expected_outputs_file) as f:
370 |             expected = set([line.strip() for line in f.readlines()])
    |
help: Replace with `cond is not None`

E711 Comparison to `None` should be `cond is not None`
   --> third_party/dawn/generator/generator_lib.py:392:36
    |
391 |     # Output the JSON tarball
392 |     if args.output_json_tarball != None:
    |                                    ^^^^
393 |         json_root = {}
394 |         for output in render_outputs:
    |
help: Replace with `cond is not None`

E711 Comparison to `None` should be `cond is not None`
   --> third_party/dawn/generator/generator_lib.py:401:27
    |
400 |     # Output the files directly.
401 |     if args.output_dir != None:
    |                           ^^^^
402 |         for output in render_outputs:
403 |             output_path = os.path.join(args.output_dir, output.name)
    |
help: Replace with `cond is not None`

E401 [*] Multiple imports on one line
  --> third_party/dawn/generator/opengl_loader_generator.py:29:1
   |
27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
29 | import os, json, sys
   | ^^^^^^^^^^^^^^^^^^^^
30 | from collections import namedtuple
31 | import xml.etree.ElementTree as etree
   |
help: Split imports

E711 Comparison to `None` should be `cond is None`
  --> third_party/dawn/generator/opengl_loader_generator.py:40:25
   |
38 |     def __init__(self, gl_name, proc_name=None):
39 |         assert gl_name.startswith('gl')
40 |         if proc_name == None:
   |                         ^^^^
41 |             proc_name = gl_name[2:]
   |
help: Replace with `cond is None`

UP032 [*] Use f-string instead of `format` call
  --> third_party/dawn/generator/opengl_loader_generator.py:56:16
   |
55 |     def __repr__(self):
56 |         return 'Proc("{}", "{}")'.format(self.gl_name, self.proc_name)
   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
help: Convert to f-string

E711 Comparison to `None` should be `cond is not None`
  --> third_party/dawn/generator/opengl_loader_generator.py:79:32
   |
77 |         def parse_type_declaration(element):
78 |             result = ''
79 |             if element.text != None:
   |                                ^^^^
80 |                 result += element.text
81 |             ptype = element.find('ptype')
   |
help: Replace with `cond is not None`

E711 Comparison to `None` should be `cond is not None`
  --> third_party/dawn/generator/opengl_loader_generator.py:82:25
   |
80 |                 result += element.text
81 |             ptype = element.find('ptype')
82 |             if ptype != None:
   |                         ^^^^
83 |                 result += ptype.text
84 |                 if ptype.tail != None:
   |
help: Replace with `cond is not None`

E711 Comparison to `None` should be `cond is not None`
  --> third_party/dawn/generator/opengl_loader_generator.py:84:34
   |
82 |             if ptype != None:
83 |                 result += ptype.text
84 |                 if ptype.tail != None:
   |                                  ^^^^
85 |                     result += ptype.tail
86 |             return result.strip()
   |
help: Replace with `cond is not None`

E711 Comparison to `None` should be `cond is not None`
   --> third_party/dawn/generator/opengl_loader_generator.py:100:37
    |
 98 |         self.gl_name = proto.find('name').text
 99 |         self.alias = None
100 |         if element.find('alias') != None:
    |                                     ^^^^
101 |             self.alias = element.find('alias').attrib['name']
    |
help: Replace with `cond is not None`

UP032 [*] Use f-string instead of `format` call
   --> third_party/dawn/generator/opengl_loader_generator.py:114:16
    |
113 |     def __repr__(self):
114 |         return 'Proc("{}")'.format(self.gl_name)
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
help: Convert to f-string

B006 Do not use mutable data structures for argument defaults
   --> third_party/dawn/generator/opengl_loader_generator.py:153:49
    |
152 |     # Get list of enums and procs per OpenGL ES/Desktop OpenGL version
153 |     def parse_version_blocks(api, removed_procs=set()):
    |                                                 ^^^^^
154 |         blocks = []
155 |         for section in root.findall('''feature[@api='{}']'''.format(api)):
    |
help: Replace with `None`; initialize within function

UP032 [*] Use f-string instead of `format` call
   --> third_party/dawn/generator/opengl_loader_generator.py:155:37
    |
153 |     def parse_version_blocks(api, removed_procs=set()):
154 |         blocks = []
155 |         for section in root.findall('''feature[@api='{}']'''.format(api)):
    |                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
156 |             section_procs = []
157 |             for command in section.findall('./require/command'):
    |
help: Convert to f-string

E711 Comparison to `None` should be `cond is None`
   --> third_party/dawn/generator/opengl_loader_generator.py:159:54
    |
157 |             for command in section.findall('./require/command'):
158 |                 proc_name = command.attrib['name']
159 |                 assert all_procs[proc_name].alias == None
    |                                                      ^^^^
160 |                 if proc_name not in removed_procs:
161 |                     section_procs.append(all_procs[proc_name])
    |
help: Replace with `cond is None`

UP032 [*] Use f-string instead of `format` call
   --> third_party/dawn/generator/opengl_loader_generator.py:178:13
    |
176 |     def parse_extension_block(extension):
177 |         section = root.find(
178 |             '''extensions/extension[@name='{}']'''.format(extension))
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
179 |         supported_specs = section.attrib['supported'].split('|')
180 |         section_procs = []
    |
help: Convert to f-string

E711 Comparison to `None` should be `cond is None`
   --> third_party/dawn/generator/opengl_loader_generator.py:183:50
    |
181 |         for command in section.findall('./require/command'):
182 |             proc_name = command.attrib['name']
183 |             assert all_procs[proc_name].alias == None
    |                                                  ^^^^
184 |             section_procs.append(all_procs[proc_name])
    |
help: Replace with `cond is None`

E713 [*] Test for membership should be `not in`
   --> third_party/dawn/generator/opengl_loader_generator.py:210:20
    |
208 |         block_procs = []
209 |         for proc in block.procs:
210 |             if not proc.glProcName() in already_added_header_procs:
    |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
211 |                 already_added_header_procs.add(proc.glProcName())
212 |                 block_procs.append(proc)
    |
help: Convert to `not in`

E713 [*] Test for membership should be `not in`
   --> third_party/dawn/generator/opengl_loader_generator.py:216:20
    |
214 |         block_enums = []
215 |         for enum in block.enums:
216 |             if not enum.name in already_added_header_enums:
    |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
217 |                 already_added_header_enums.add(enum.name)
218 |                 block_enums.append(enum)
    |
help: Convert to `not in`

UP032 [*] Use f-string instead of `format` call
   --> third_party/dawn/generator/opengl_loader_generator.py:226:13
    |
224 |     for block in gles_blocks:
225 |         add_header_block(
226 |             'OpenGL ES {}.{}'.format(block.version.major, block.version.minor),
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
227 |             block)
    |
help: Convert to f-string

UP032 [*] Use f-string instead of `format` call
   --> third_party/dawn/generator/opengl_loader_generator.py:231:13
    |
229 |       for block in desktop_gl_blocks:
230 |           add_header_block(
231 | /             'Desktop OpenGL {}.{}'.format(block.version.major,
232 | |                                           block.version.minor), block)
    | |______________________________________________________________^
233 |
234 |       for block in extension_desktop_gl_blocks:
    |
help: Convert to f-string

E501 Line too long (90 > 88)
   --> third_party/dawn/generator/webgpu_docs_utility.py:119:89
    |
117 |             log_warning(
118 |                 log_file,
119 |                 f"Missing documentation for all {part_type} of {item_type} '{item_name}'."
    |                                                                                         ^^
120 |             )
121 |         else:
    |

E501 Line too long (115 > 88)
   --> third_party/dawn/generator/webgpu_docs_utility.py:124:89
    |
122 |             log_warning(
123 |                 log_file,
124 |                 f"Missing documentation for {part_type} of {item_type} '{item_name}': {', '.join(missing_parts)}. "
    |                                                                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^
125 |                 f"(Found for: {', '.join(found_parts)})")
126 |         return False
    |

E501 Line too long (91 > 88)
   --> third_party/dawn/generator/webgpu_docs_utility.py:138:89
    |
136 |     """Cleans a docstring by validating and formatting references for Kotlin KDoc."""
137 |     # These are special-cased references that are not part of the API but are
138 |     # used in the documentation. They are manually mapped in the `dawn_kotlin` JSON config.
    |                                                                                         ^^^
139 |     KOTLIN_HARDCODED_LINK_REFS = [
140 |         "CallbackStatuses",
    |

E501 Line too long (90 > 88)
   --> third_party/dawn/generator/webgpu_docs_utility.py:217:89
    |
216 |             def replacer(match):
217 |                 """Converts a matched object-method name to a qualified KDoc @see link."""
    |                                                                                         ^^
218 |                 object_name = match.group(
219 |                     1)  # The object name, e.g., "Instance"
    |

B023 Function definition does not bind loop variable `methods`
   --> third_party/dawn/generator/webgpu_docs_utility.py:222:31
    |
220 |                 arg_name = match.group(2)  # The arg name, e.g., "WaitAny"
221 |                 y_camel = arg_name[0].lower() + arg_name[1:]  # e.g., "waitAny"
222 |                 if y_camel in methods:
    |                               ^^^^^^^
223 |                     kotlin_str = f'{object_name}.{y_camel}'
224 |                     return kotlin_str if "@see" in doc else f'@see {kotlin_str}'
    |

B023 Function definition does not bind loop variable `doc`
   --> third_party/dawn/generator/webgpu_docs_utility.py:224:52
    |
222 |                 if y_camel in methods:
223 |                     kotlin_str = f'{object_name}.{y_camel}'
224 |                     return kotlin_str if "@see" in doc else f'@see {kotlin_str}'
    |                                                    ^^^
225 |                 return match.group(0)  # Return original if not a valid method
    |

SIM118 Use `key in dict` instead of `key in dict.keys()`
   --> third_party/dawn/generator/webgpu_docs_utility.py:259:13
    |
257 |     # Prefixes object names with 'GPU' for Kotlin references.
258 |     if object_methods:
259 |         for obj_name in object_methods.keys():
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
260 |             doc = re.sub(r'\b' + re.escape(obj_name) + r'\b', 'GPU' + obj_name,
261 |                          doc)
    |
help: Remove `.keys()`

E501 Line too long (89 > 88)
   --> third_party/dawn/generator/webgpu_docs_utility.py:431:89
    |
429 |         by_category_lookups[data_category] = name_to_item_map
430 |
431 |     # Iterate through categorized API data and extract docs from corresponding JSON data.
    |                                                                                         ^
432 |     for category, items in by_category.items():
433 |         data_category = category_map.get(category)
    |

SIM102 Use a single `if` statement instead of nested `if` statements
   --> third_party/dawn/generator/webgpu_docs_utility.py:472:13
    |
470 |                   if not doc_node or not doc_node.strip():
471 |                       is_main_doc_missing = True
472 | /             elif isinstance(doc_node, dict):
473 | |                 if not doc_node.get('doc') or not doc_node.get('doc').strip():
    | |______________________________________________________________________________^
474 |                       is_main_doc_missing = True
    |
help: Combine `if` statements using `and`

E501 Line too long (89 > 88)
   --> third_party/dawn/generator/webgpu_docs_utility.py:502:89
    |
500 |                     log_warning(
501 |                         log_file,
502 |                         f"Missing documentation for all methods of object '{item_name}'."
    |                                                                                         ^
503 |                     )
504 |                     continue
    |

E501 Line too long (90 > 88)
   --> third_party/dawn/generator/webgpu_docs_utility.py:517:89
    |
515 |                         log_warning(
516 |                             log_file,
517 |                             f"Missing main documentation for method '{method_full_name}'."
    |                                                                                         ^^
518 |                         )
    |

E501 Line too long (112 > 88)
   --> third_party/dawn/generator/webgpu_docs_utility.py:531:89
    |
530 | def cleanup_doc_map(doc_map, by_category, params):
531 |     """Post-processes the documentation map to fix and validate cross-references for a specific target language.
    |                                                                                         ^^^^^^^^^^^^^^^^^^^^^^^^
532 |
533 |   Args:
    |

E501 Line too long (90 > 88)
   --> third_party/dawn/generator/webgpu_docs_utility.py:570:89
    |
569 |     def recursive_clean(d):
570 |         """Recursively traverses the doc map and applies the language-specific cleaner."""
    |                                                                                         ^^
571 |         if isinstance(d, dict):
572 |             for k, v in d.items():
    |

UP031 Use format specifiers instead of percent format
  --> third_party/dawn/go_presubmit_support.py:61:50
   |
59 |             cwd=input_api.PresubmitLocalPath())
60 |     except input_api.subprocess.CalledProcessError as e:
61 |         results.append(output_api.PresubmitError('%s' % (e, )))
   |                                                  ^^^^^^^^^^^^
62 |     return results
   |
help: Replace with format specifiers

UP031 Use format specifiers instead of percent format
  --> third_party/dawn/go_presubmit_support.py:80:21
   |
78 |               errors.append(
79 |                   output_api.PresubmitError(
80 | /                     'Go code in %s is not formatted, please run "gofmt -w %s"'
81 | |                     % (path, full_path)))
   | |_______________________________________^
82 |       except input_api.subprocess.CalledProcessError as e:
83 |           errors.append(output_api.PresubmitError('EnforceGoFormatting: %s' % e))
   |
help: Replace with format specifiers

UP031 Use format specifiers instead of percent format
  --> third_party/dawn/go_presubmit_support.py:83:49
   |
81 |                     % (path, full_path)))
82 |     except input_api.subprocess.CalledProcessError as e:
83 |         errors.append(output_api.PresubmitError('EnforceGoFormatting: %s' % e))
   |                                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
84 |     return errors
   |
help: Replace with format specifiers

UP031 Use format specifiers instead of percent format
   --> third_party/dawn/infra/config/scripts/milestones.py:109:28
    |
107 | def add_milestone(milestones, milestone_num, branch):
108 |     if str(milestone_num) in milestones:
109 |         raise RuntimeError('Milestone %d already exists' % milestone_num)
    |                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
110 |
111 |     milestones[str(milestone_num)] = {
    |
help: Replace with format specifiers

UP031 Use format specifiers instead of percent format
   --> third_party/dawn/infra/config/scripts/milestones.py:127:28
    |
125 | def remove_milestone(milestones, milestone_num):
126 |     if str(milestone_num) not in milestones:
127 |         raise RuntimeError('Milestone %d does not exist' % milestone_num)
    |                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
128 |     del milestones[str(milestone_num)]
129 |     # Not strictly necessary, but returning a value keeps this consistent with
    |
help: Replace with format specifiers

F401 [*] `logging` imported but unused
  --> third_party/dawn/infra/specs/generate_test_spec_json.py:30:8
   |
29 | import argparse
30 | import logging
   |        ^^^^^^^
31 | import os
32 | import pprint
   |
help: Remove unused import: `logging`

UP035 `typing.Dict` is deprecated, use `dict` instead
  --> third_party/dawn/infra/specs/generate_test_spec_json.py:35:1
   |
33 | import sys
34 | import tempfile
35 | from typing import Dict, List, Optional, Tuple
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
36 |
37 | # //testing/buildbot is only retrieved via DEPS for standalone checkouts.
   |

UP035 `typing.List` is deprecated, use `list` instead
  --> third_party/dawn/infra/specs/generate_test_spec_json.py:35:1
   |
33 | import sys
34 | import tempfile
35 | from typing import Dict, List, Optional, Tuple
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
36 |
37 | # //testing/buildbot is only retrieved via DEPS for standalone checkouts.
   |

UP035 `typing.Tuple` is deprecated, use `tuple` instead
  --> third_party/dawn/infra/specs/generate_test_spec_json.py:35:1
   |
33 | import sys
34 | import tempfile
35 | from typing import Dict, List, Optional, Tuple
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
36 |
37 | # //testing/buildbot is only retrieved via DEPS for standalone checkouts.
   |

E402 Module level import not at top of file
  --> third_party/dawn/infra/specs/generate_test_spec_json.py:50:1
   |
49 | sys.path.insert(0, TESTING_BUILDBOT_DIR)
50 | import generate_buildbot_json
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
51 |
52 | # Add custom mixins here.
   |

UP006 [*] Use `dict` instead of `Dict` for type annotation
   --> third_party/dawn/infra/specs/generate_test_spec_json.py:217:63
    |
216 | def _get_trimmed_mixins(
217 |         generator: generate_buildbot_json.BBJSONGenerator) -> Dict[str, dict]:
    |                                                               ^^^^
218 |     """Helper function to get a trimmed set of mixins.
    |
help: Replace with `dict`

UP006 [*] Use `list` instead of `List` for type annotation
   --> third_party/dawn/infra/specs/generate_test_spec_json.py:278:36
    |
278 | def _run_generator(generator_args: List[str],
    |                                    ^^^^
279 |                    output_dir: Optional[str] = None) -> None:
280 |     """Runs the generate_buildbot_json script for Dawn.
    |
help: Replace with `list`

UP045 [*] Use `X | None` for type annotations
   --> third_party/dawn/infra/specs/generate_test_spec_json.py:279:32
    |
278 | def _run_generator(generator_args: List[str],
279 |                    output_dir: Optional[str] = None) -> None:
    |                                ^^^^^^^^^^^^^
280 |     """Runs the generate_buildbot_json script for Dawn.
    |
help: Convert to `X | None`

E711 Comparison to `None` should be `cond is not None`
   --> third_party/dawn/infra/specs/generate_test_spec_json.py:290:33
    |
288 |             files to disk.
289 |     """
290 |     verify_only = output_dir != None
    |                                 ^^^^
291 |
292 |     assert '--pyl-files-dir' not in generator_args
    |
help: Replace with `cond is not None`

UP006 [*] Use `tuple` instead of `Tuple` for type annotation
   --> third_party/dawn/infra/specs/generate_test_spec_json.py:321:22
    |
321 | def _parse_args() -> Tuple[argparse.Namespace, List[str]]:
    |                      ^^^^^
322 |     """Parses known and unknown args."""
323 |     parser = argparse.ArgumentParser(
    |
help: Replace with `tuple`

UP006 [*] Use `list` instead of `List` for type annotation
   --> third_party/dawn/infra/specs/generate_test_spec_json.py:321:48
    |
321 | def _parse_args() -> Tuple[argparse.Namespace, List[str]]:
    |                                                ^^^^
322 |     """Parses known and unknown args."""
323 |     parser = argparse.ArgumentParser(
    |
help: Replace with `list`

F401 [*] `platform` imported but unused
  --> third_party/dawn/scripts/dawn_node_cts/node_helpers.py:31:8
   |
29 | import functools
30 | import os
31 | import platform
   |        ^^^^^^^^
32 | import sys
   |
help: Remove unused import: `platform`

E402 Module level import not at top of file
  --> third_party/dawn/scripts/dawn_node_cts/node_helpers.py:39:1
   |
37 | sys.path.insert(0, DAWN_ROOT)
38 |
39 | from tools.python import cipd_deps
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |

F401 [*] `functools` imported but unused
  --> third_party/dawn/scripts/dawn_node_cts/run_dawn_node_cts.py:32:8
   |
30 | import argparse
31 | import contextlib
32 | import functools
   |        ^^^^^^^^^
33 | import glob
34 | import json
   |
help: Remove unused import: `functools`

F401 [*] `glob` imported but unused
  --> third_party/dawn/scripts/dawn_node_cts/run_dawn_node_cts.py:33:8
   |
31 | import contextlib
32 | import functools
33 | import glob
   |        ^^^^
34 | import json
35 | import logging
   |
help: Remove unused import: `glob`

UP004 [*] Class `FileEntry` inherits from `object`
  --> third_party/dawn/scripts/extract.py:37:17
   |
37 | class FileEntry(object):
   |                 ^^^^^^
38 |
39 |     def __init__(self, path, mode, fileobj):
   |
help: Remove `object` inheritance

UP004 [*] Class `SymlinkEntry` inherits from `object`
  --> third_party/dawn/scripts/extract.py:45:20
   |
45 | class SymlinkEntry(object):
   |                    ^^^^^^
46 |
47 |     def __init__(self, path, mode, target):
   |
help: Remove `object` inheritance

UP031 Use format specifiers instead of percent format
  --> third_party/dawn/scripts/extract.py:80:34
   |
78 |                                 tar_file.extractfile(info))
79 |             else:
80 |                 raise ValueError('Unknown entry type "%s"' % (info.name, ))
   |                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
help: Replace with format specifiers

UP031 Use format specifiers instead of percent format
   --> third_party/dawn/scripts/extract.py:130:19
    |
128 |     try:
129 |         if os.path.exists(output):
130 |             print("Removing %s" % (output, ))
    |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^
131 |             shutil.rmtree(output)
    |
help: Replace with format specifiers

UP031 Use format specifiers instead of percent format
   --> third_party/dawn/scripts/extract.py:133:15
    |
131 |             shutil.rmtree(output)
132 |
133 |         print("Extracting %s to %s" % (archive, output))
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
134 |         prefix = None
135 |         num_extracted = 0
    |
help: Replace with format specifiers

UP031 Use format specifiers instead of percent format
   --> third_party/dawn/scripts/extract.py:173:23
    |
171 |             num_extracted += 1
172 |             if num_extracted % 100 == 0:
173 |                 print("Extracted %d files..." % (num_extracted, ))
    |                       ^^^^^^^^^^^^^^^^^^^^^^^
174 |     finally:
175 |         entries.close()
    |
help: Replace with format specifiers

UP031 Use format specifiers instead of percent format
   --> third_party/dawn/scripts/extract.py:180:11
    |
178 |         f.write(digest)
179 |
180 |     print("Done. Extracted %d files." % (num_extracted, ))
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
181 |     return 0
    |
help: Replace with format specifiers

F841 Local variable `p` is assigned to but never used
  --> third_party/dawn/scripts/merge_scripts/fuzz_corpora_common.py:74:5
   |
72 |         f'gs://{BUCKET}/{BUCKET_DIRECTORY}/{fuzzer_name}',
73 |     ])
74 |     p = subprocess.run(cmd, check=True)
   |     ^
   |
help: Remove assignment to unused variable `p`

E402 Module level import not at top of file
  --> third_party/dawn/scripts/merge_scripts/generate_tint_fuzz_corpora.py:45:1
   |
43 | sys.path.insert(0, DAWN_ROOT)
44 |
45 | from scripts.merge_scripts import fuzz_corpora_common
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
46 |
47 | try:
   |

E402 Module level import not at top of file
  --> third_party/dawn/scripts/merge_scripts/generate_wire_trace_fuzz_corpora.py:44:1
   |
42 | sys.path.insert(0, DAWN_ROOT)
43 |
44 | from scripts.merge_scripts import fuzz_corpora_common
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
45 |
46 | try:
   |

E501 Line too long (111 > 88)
  --> third_party/dawn/scripts/perf_test_runner.py:41:89
   |
39 | # Dawn locates at /path/to/Chromium/src/third_party/dawn/
40 | # Chromium build usually locates at /path/to/Chromium/src/out/Release/
41 | # You might want to change the base_path if you want to run dawn_perf_tests build from a Dawn standalone build.
   |                                                                                         ^^^^^^^^^^^^^^^^^^^^^^^
42 | base_path = os.path.abspath(
43 |     os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../../'))
   |

E711 Comparison to `None` should be `cond is None`
   --> third_party/dawn/scripts/perf_test_runner.py:114:22
    |
112 | perftests_path = newest_binary
113 |
114 | if perftests_path == None or not os.path.exists(perftests_path):
    |                      ^^^^
115 |     print('Cannot find Release %s!' % binary_name)
116 |     sys.exit(1)
    |
help: Replace with `cond is None`

UP031 Use format specifiers instead of percent format
   --> third_party/dawn/scripts/perf_test_runner.py:115:11
    |
114 | if perftests_path == None or not os.path.exists(perftests_path):
115 |     print('Cannot find Release %s!' % binary_name)
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
116 |     sys.exit(1)
    |
help: Replace with format specifiers

B006 Do not use mutable data structures for argument defaults
   --> third_party/dawn/scripts/perf_test_runner.py:125:36
    |
125 | def get_results(metric, extra_args=[]):
    |                                    ^^
126 |     process = subprocess.Popen(
127 |         [perftests_path, '--gtest_filter=' + test_name] + extra_args,
    |
help: Replace with `None`; initialize within function

UP031 Use format specifiers instead of percent format
   --> third_party/dawn/scripts/perf_test_runner.py:143:15
    |
141 |     m = re.findall(pattern, output_string)
142 |     if not m:
143 |         print("Did not find the metric '%s' in the test output:" % metric)
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
144 |         print(output_string)
145 |         sys.exit(1)
    |
help: Replace with format specifiers

UP031 Use format specifiers instead of percent format
   --> third_party/dawn/scripts/perf_test_runner.py:152:7
    |
150 | # Calibrate the number of steps
151 | steps = get_results("steps", ["--calibration"])[0]
152 | print("running with %d steps." % steps)
    |       ^^^^^^^^^^^^^^^^^^^^^^^^
153 |
154 | # Loop 'max_experiments' times, running the tests.
    |
help: Replace with format specifiers

B007 Loop control variable `experiment` not used within loop body
   --> third_party/dawn/scripts/perf_test_runner.py:155:5
    |
154 | # Loop 'max_experiments' times, running the tests.
155 | for experiment in range(max_experiments):
    |     ^^^^^^^^^^
156 |     experiment_scores = get_results(metric, ["--override-steps", str(steps)])
    |
help: Rename unused `experiment` to `_experiment`

UP031 Use format specifiers instead of percent format
   --> third_party/dawn/scripts/perf_test_runner.py:159:26
    |
158 |     for score in experiment_scores:
159 |         sys.stdout.write("%s: %.2f" % (metric, score))
    |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
160 |         scores.append(score)
    |
help: Replace with format specifiers

UP031 Use format specifiers instead of percent format
   --> third_party/dawn/scripts/perf_test_runner.py:163:30
    |
162 |         if (len(scores) > 1):
163 |             sys.stdout.write(", mean: %.2f" % mean(scores))
    |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
164 |             sys.stdout.write(", variation: %.2f%%" %
165 |                              (coefficient_of_variation(scores) * 100.0))
    |
help: Replace with format specifiers

UP031 Use format specifiers instead of percent format
   --> third_party/dawn/scripts/perf_test_runner.py:169:30
    |
167 |           if (len(scores) > 7):
168 |               truncation_n = len(scores) >> 3
169 |               sys.stdout.write(", truncated mean: %.2f" %
    |  ______________________________^
170 | |                              truncated_mean(scores, truncation_n))
    | |_________________________________________________________________^
171 |               sys.stdout.write(", variation: %.2f%%" %
172 |                                (truncated_cov(scores, truncation_n) * 100.0))
    |
help: Replace with format specifiers

UP035 `typing.Type` is deprecated, use `type` instead
  --> third_party/dawn/scripts/roll_chromium_deps.py:47:1
   |
45 | import subprocess
46 | import sys
47 | from typing import Any, Self, Type
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
48 |
49 | import requests
   |

F541 [*] f-string without any placeholders
   --> third_party/dawn/scripts/roll_chromium_deps.py:821:9
    |
819 |     chromium_lines.extend([
820 |         f'Roll chromium_revision {chromium_revision_range.revision_range()}',
821 |         f'',
    |         ^^^
822 |         f'Change log: {chromium_revision_range.log_link()}',
823 |         f'Full diff: {chromium_revision_range.commit_link()}',
    |
help: Remove extraneous `f` prefix

UP006 [*] Use `type` instead of `Type` for type annotation
   --> third_party/dawn/scripts/roll_chromium_deps.py:848:50
    |
848 | def _generate_section_for_entry_type(entry_type: Type[ChangedDepsEntry],
    |                                                  ^^^^
849 |                                      changed_entries: list[ChangedDepsEntry],
850 |                                      empty_message: str,
    |
help: Replace with `type`

E501 Line too long (91 > 88)
  --> third_party/dawn/scripts/run_tint_benchmark_test.py:30:89
   |
28 | """Runs the Tint benchmark to check that it works.
29 |
30 | This script is necessary in order to support running the Tint benchmark on Swarming, but it
   |                                                                                         ^^^
31 | is effectively a thin wrapper around the underlying tint_benchmark binary.
32 | """
   |

UP015 [*] Unnecessary mode argument
  --> third_party/dawn/src/dawn/node/gen_napi_symbols.py:38:28
   |
36 | output_file = Path(sys.argv[2])
37 |
38 | with open(symbols_js_file, "r") as f:
   |                            ^^^
39 |     matches = re.findall(r"napi_[a-z0-9_]*", f.read())
   |
help: Remove mode argument

UP035 `typing.Dict` is deprecated, use `dict` instead
  --> third_party/dawn/src/emdawnwebgpu/pkg/emdawnwebgpu.port.py:36:1
   |
34 | import sys
35 | import zlib
36 | from typing import Dict, Optional
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
37 |
38 | # Imports from Emscripten
   |

E501 Line too long (166 > 88)
  --> third_party/dawn/src/emdawnwebgpu/pkg/emdawnwebgpu.port.py:45:89
   |
43 | …
44 | …
45 | …cripten's license (available under both MIT License and University of Illinois/NCSA Open Source License)"
   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
46 |
47 |
   |

E501 Line too long (92 > 88)
  --> third_party/dawn/src/emdawnwebgpu/pkg/emdawnwebgpu.port.py:50:89
   |
48 | OPTIONS = {
49 |     'cpp_bindings':
50 |     "Add the include path for Dawn-like <webgpu/webgpu_cpp.h> C++ bindings. Default: true.",
   |                                                                                         ^^^^
51 |
52 |     # The following options are generally not needed, as they are automatically
   |

E501 Line too long (99 > 88)
  --> third_party/dawn/src/emdawnwebgpu/pkg/emdawnwebgpu.port.py:59:89
   |
57 |     #     embuilder build path/to/port/file.py:opt_level=2:shared_memory=true
58 |     'opt_level':
59 |     "Optimization (-O) level for the bindings' Wasm layer. Default: choose based on -sASSERTIONS.",
   |                                                                                         ^^^^^^^^^^^
60 |     'shared_memory':
61 |     "Enable -sSHARED_MEMORY. Default: choose based on whether linker has -sSHARED_MEMORY enabled.",
   |

E501 Line too long (99 > 88)
  --> third_party/dawn/src/emdawnwebgpu/pkg/emdawnwebgpu.port.py:61:89
   |
59 |     "Optimization (-O) level for the bindings' Wasm layer. Default: choose based on -sASSERTIONS.",
60 |     'shared_memory':
61 |     "Enable -sSHARED_MEMORY. Default: choose based on whether linker has -sSHARED_MEMORY enabled.",
   |                                                                                         ^^^^^^^^^^^
62 | }
63 | _VALID_OPTION_VALUES = {
   |

UP006 [*] Use `dict` instead of `Dict` for type annotation
  --> third_party/dawn/src/emdawnwebgpu/pkg/emdawnwebgpu.port.py:68:8
   |
66 |     'shared_memory': ['auto', 'true', 'false'],
67 | }
68 | _opts: Dict[str, Optional[str]] = {
   |        ^^^^
69 |     'cpp_bindings': 'true',
70 |     'opt_level': 'auto',
   |
help: Replace with `dict`

UP045 [*] Use `X | None` for type annotations
  --> third_party/dawn/src/emdawnwebgpu/pkg/emdawnwebgpu.port.py:68:18
   |
66 |     'shared_memory': ['auto', 'true', 'false'],
67 | }
68 | _opts: Dict[str, Optional[str]] = {
   |                  ^^^^^^^^^^^^^
69 |     'cpp_bindings': 'true',
70 |     'opt_level': 'auto',
   |
help: Convert to `X | None`

B007 Loop control variable `dirnames` not used within loop body
  --> third_party/dawn/src/emdawnwebgpu/pkg/emdawnwebgpu.port.py:76:19
   |
75 | def _walk(path):
76 |     for (dirpath, dirnames, filenames) in os.walk(path):
   |                   ^^^^^^^^
77 |         for filename in filenames:
78 |             yield os.path.join(dirpath, filename)
   |
help: Rename unused `dirnames` to `_dirnames`

E501 Line too long (95 > 88)
  --> third_party/dawn/src/emdawnwebgpu/pkg/emdawnwebgpu.port.py:92:89
   |
90 | if not os.path.isfile(os.path.join(_c_include_dir, 'webgpu', 'webgpu.h')):
91 |     diagnostics.error(
92 |         "emdawnwebgpu.port.py must sit in a built emdawnwebgpu_pkg, not be used standalone or "
   |                                                                                         ^^^^^^^
93 |         "from Dawn's source tree. Download a pre-built package from "
94 |         "https://github.com/google/dawn/releases or build it locally.")
   |

SIM115 Use a context manager for opening files
   --> third_party/dawn/src/emdawnwebgpu/pkg/emdawnwebgpu.port.py:178:13
    |
177 |     for filename in _files_affecting_port_build:
178 |         add(open(filename, 'rb').read())
    |             ^^^^
179 |
180 |     (lib_name_suffix, _) = _compute_library_compile_flags(settings)
    |

E501 Line too long (101 > 88)
   --> third_party/dawn/src/emdawnwebgpu/pkg/emdawnwebgpu.port.py:190:89
    |
188 |     if not settings.LINK_AS_CXX:
189 |         diagnostics.error(
190 |             'emdawnwebgpu requires C++. Link using em++ (or emcc -sDEFAULT_TO_CXX), instead of emcc.'
    |                                                                                         ^^^^^^^^^^^^^
191 |         )
    |

SIM118 Use `key in dict` instead of `key in dict.keys()`
   --> third_party/dawn/src/emdawnwebgpu/pkg/emdawnwebgpu.port.py:202:8
    |
200 |     ]
201 |
202 |     if 'CLOSURE_ARGS' in settings.keys():
    |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
203 |         # This works in Emscripten >4.0.7. In <=4.0.7, the user has to pass it.
204 |         settings.CLOSURE_ARGS += [
    |
help: Remove `.keys()`

E501 Line too long (98 > 88)
  --> third_party/dawn/src/tint/cmd/bench/generate_benchmark_inputs.py:30:89
   |
28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 | """
30 | Generates a header file that declares all of the Tint benchmark programs as embedded WGSL shaders,
   |                                                                                         ^^^^^^^^^^
31 | and declares macros that will be used to register them all with Google Benchmark.
   |

UP015 [*] Unnecessary mode argument
   --> third_party/dawn/src/tint/cmd/bench/generate_benchmark_inputs.py:107:34
    |
106 |             # Rewrite the file without CRLF line endings.
107 |             with open(wgsl_path, 'r') as file:
    |                                  ^^^
108 |                 wgsl = file.read()
109 |             with open(wgsl_path, 'w', newline='\n') as file:
    |
help: Remove mode argument

SIM102 Use a single `if` statement instead of nested `if` statements
   --> third_party/dawn/src/tint/cmd/bench/generate_benchmark_inputs.py:113:13
    |
112 |               # Check if the generated content is different to the current file.
113 | /             if args.check_stale:
114 | |                 if not filecmp.cmp(
115 | |                         tmp_wgsl_path, final_wgsl_path, shallow=False):
    | |_______________________________________________________________________^
116 |                       print(f'{final_wgsl_path} is stale')
117 |                       print()
    |
help: Combine `if` statements using `and`

E501 Line too long (90 > 88)
   --> third_party/dawn/src/tint/cmd/bench/generate_benchmark_inputs.py:178:89
    |
176 |                 for char in input.read():
177 |                     if char == ord('\r'):
178 |                         # Skip carriage return to make output consistent across platforms.
    |                                                                                         ^^
179 |                         continue
180 |                     if (i % 16) == 0:
    |

F541 [*] f-string without any placeholders
   --> third_party/dawn/src/tint/cmd/bench/generate_benchmark_inputs.py:184:23
    |
182 |                     print(' ' + str(char), file=output, end=',')
183 |                     i += 1
184 |                 print(f'}}}},', file=output)
    |                       ^^^^^^^^
185 |
186 |         print('};', file=output)
    |
help: Remove extraneous `f` prefix

E501 Line too long (97 > 88)
  --> third_party/dawn/src/tint/tint_gdb.py:37:89
   |
35 | from itertools import chain
36 |
37 | # When debugging this module, set _DEBUGGING = True so that re-sourcing this file in gdb replaces
   |                                                                                         ^^^^^^^^^
38 | # the existing printers.
39 | _DEBUGGING = True
   |

E501 Line too long (103 > 88)
  --> third_party/dawn/src/tint/tint_gdb.py:41:89
   |
39 | _DEBUGGING = True
40 |
41 | # Enable to display other data members along with child elements of compound data types (arrays, etc.).
   |                                                                                         ^^^^^^^^^^^^^^^
42 | # This is useful in debuggers like VS Code that doesn't display the `to_string()` result in the watch window.
43 | # OTOH, it's less useful when using gdb/lldb's print command.
   |

E501 Line too long (109 > 88)
  --> third_party/dawn/src/tint/tint_gdb.py:42:89
   |
41 | # Enable to display other data members along with child elements of compound data types (arrays, etc.).
42 | # This is useful in debuggers like VS Code that doesn't display the `to_string()` result in the watch window.
   |                                                                                         ^^^^^^^^^^^^^^^^^^^^^
43 | # OTOH, it's less useful when using gdb/lldb's print command.
44 | _DISPLAY_MEMBERS_AS_CHILDREN = False
   |

E501 Line too long (94 > 88)
  --> third_party/dawn/src/tint/tint_gdb.py:47:89
   |
46 | # Tips for debugging using VS Code:
47 | # - Set a breakpoint where you can view the types you want to debug/write pretty printers for.
   |                                                                                         ^^^^^^
48 | # - Debug Console: source /path/to/dawn/src/tint/tint_gdb.py
49 | # - To execute Python code, in the Debug Console:
   |

E501 Line too long (89 > 88)
  --> third_party/dawn/src/tint/tint_gdb.py:51:89
   |
49 | # - To execute Python code, in the Debug Console:
50 | #   -exec python foo = gdb.parse_and_eval('map.set_')
51 | #   -exec python v = (foo['slots_']['impl_']['slice']['data'] + 8).dereference()['value']
   |                                                                                         ^
52 | #
53 | # - Useful docs:
   |

UP004 [*] Class `Printer` inherits from `object`
  --> third_party/dawn/src/tint/tint_gdb.py:62:15
   |
62 | class Printer(object):
   |               ^^^^^^
63 |     '''Base class for Printers'''
   |
help: Remove `object` inheritance

UP008 [*] Use `super()` instead of `super(__class__, self)`
  --> third_party/dawn/src/tint/tint_gdb.py:77:14
   |
76 |     def __init__(self, val):
77 |         super(UtilsSlicePrinter, self).__init__(val)
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^
78 |         self.len = self.val['len']
79 |         self.cap = self.val['cap']
   |
help: Remove `super()` parameters

UP032 [*] Use f-string instead of `format` call
  --> third_party/dawn/src/tint/tint_gdb.py:91:16
   |
90 |     def to_string(self):
91 |         return 'length={} capacity={}'.format(self.len, self.cap)
   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
92 |
93 |     def members(self):
   |
help: Convert to f-string

UP028 Replace `yield` over `for` loop with `yield from`
   --> third_party/dawn/src/tint/tint_gdb.py:103:9
    |
102 |       def children(self):
103 | /         for m in self.members():
104 | |             yield m
    | |___________________^
105 |           for i in range(self.len):
106 |               yield str(i), self.value_at(i)
    |
help: Replace with `yield from`

UP008 [*] Use `super()` instead of `super(__class__, self)`
   --> third_party/dawn/src/tint/tint_gdb.py:119:14
    |
118 |     def __init__(self, val):
119 |         super(UtilsVectorPrinter, self).__init__(val)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^
120 |         self.slice = self.val['impl_']['slice']
121 |         self.using_heap = self.slice['cap'] > self.template_type(1)
    |
help: Remove `super()` parameters

UP032 [*] Use f-string instead of `format` call
   --> third_party/dawn/src/tint/tint_gdb.py:127:16
    |
126 |     def to_string(self):
127 |         return 'heap={} {}'.format(self.using_heap, self.slice)
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
128 |
129 |     def members(self):
    |
help: Convert to f-string

UP008 [*] Use `super()` instead of `super(__class__, self)`
   --> third_party/dawn/src/tint/tint_gdb.py:151:14
    |
150 |     def __init__(self, val):
151 |         super(UtilsVectorRefPrinter, self).__init__(val)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
152 |         self.slice = self.val['slice_']
153 |         self.can_move = self.val['can_move_']
    |
help: Remove `super()` parameters

UP032 [*] Use f-string instead of `format` call
   --> third_party/dawn/src/tint/tint_gdb.py:156:16
    |
155 |     def to_string(self):
156 |         return 'can_move={} {}'.format(self.can_move, self.slice)
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
157 |
158 |     def members(self):
    |
help: Convert to f-string

UP008 [*] Use `super()` instead of `super(__class__, self)`
   --> third_party/dawn/src/tint/tint_gdb.py:181:14
    |
180 |     def __init__(self, val):
181 |         super(UtilsHashmapBasePrinter, self).__init__(val)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
182 |         self.slice = UtilsVectorPrinter(self.val['slots_']).slice_printer()
183 |         self.try_read_std_optional_func = self.try_read_std_optional
    |
help: Remove `super()` parameters

UP032 [*] Use f-string instead of `format` call
   --> third_party/dawn/src/tint/tint_gdb.py:191:16
    |
189 |             if v['hash'] != 0:
190 |                 length += 1
191 |         return 'length={}'.format(length)
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^
192 |
193 |     def children(self):
    |
help: Convert to f-string

E722 Do not use bare `except`
   --> third_party/dawn/src/tint/tint_gdb.py:223:9
    |
221 |             v = entry['_M_payload']['_M_payload']['_M_value']
222 |             return slot, v
223 |         except:
    |         ^^^^^^
224 |             return None
    |

E722 Do not use bare `except`
   --> third_party/dawn/src/tint/tint_gdb.py:238:9
    |
236 |             kvp = entry['_M_payload']['_M_payload']['_M_value']
237 |             return str(kvp['key']), kvp['value']
238 |         except:
    |         ^^^^^^
239 |             return None
    |

E501 Line too long (89 > 88)
  --> third_party/dawn/src/tint/tint_lldb.py:30:89
   |
28 | # Pretty printers for the Tint project.
29 | #
30 | # If using lldb from command line, add a line to your ~/.lldbinit to import the printers:
   |                                                                                         ^
31 | #
32 | #    command script import /path/to/dawn/src/tint/tint_lldb.py
   |

E501 Line too long (94 > 88)
  --> third_party/dawn/src/tint/tint_lldb.py:68:89
   |
66 | # Tips for debugging using VS Code:
67 | #
68 | # - Set a breakpoint where you can view the types you want to debug/write pretty printers for.
   |                                                                                         ^^^^^^
69 | # - Debug Console: -exec command script import /path/to/dawn/src/tint/tint_lldb.py
70 | # - You can re-run the above command to reload the printers after modifying the python script.
   |

E501 Line too long (94 > 88)
  --> third_party/dawn/src/tint/tint_lldb.py:70:89
   |
68 | # - Set a breakpoint where you can view the types you want to debug/write pretty printers for.
69 | # - Debug Console: -exec command script import /path/to/dawn/src/tint/tint_lldb.py
70 | # - You can re-run the above command to reload the printers after modifying the python script.
   |                                                                                         ^^^^^^
71 |
72 | # - Useful docs:
   |

UP010 [*] Unnecessary `__future__` imports `division`, `print_function` for target Python version
  --> third_party/dawn/src/tint/tint_lldb.py:79:1
   |
77 | #     SBValue: https://lldb.llvm.org/python_api/lldb.SBValue.html
78 |
79 | from __future__ import print_function, division
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
80 | import sys
81 | import logging
   |
help: Remove unnecessary `__future__` import

F401 [*] `re` imported but unused
  --> third_party/dawn/src/tint/tint_lldb.py:82:8
   |
80 | import sys
81 | import logging
82 | import re
   |        ^^
83 | import lldb
84 | import types
   |
help: Remove unused import: `re`

F401 [*] `types` imported but unused
  --> third_party/dawn/src/tint/tint_lldb.py:84:8
   |
82 | import re
83 | import lldb
84 | import types
   |        ^^^^^
85 |
86 | if sys.version_info[0] == 2:
   |
help: Remove unused import: `types`

UP036 Version block is outdated for minimum Python version
  --> third_party/dawn/src/tint/tint_lldb.py:86:4
   |
84 | import types
85 |
86 | if sys.version_info[0] == 2:
   |    ^^^^^^^^^^^^^^^^^^^^^^^^
87 |     # python2-based LLDB accepts utf8-encoded ascii strings only.
88 |     def to_lldb_str(s):
   |
help: Remove outdated version block

F821 Undefined name `unicode`
  --> third_party/dawn/src/tint/tint_lldb.py:90:16
   |
88 |     def to_lldb_str(s):
89 |         return s.encode('utf8', 'backslashreplace') if isinstance(
90 |             s, unicode) else s
   |                ^^^^^^^
91 |
92 |     range = xrange
   |

F821 Undefined name `xrange`
  --> third_party/dawn/src/tint/tint_lldb.py:92:13
   |
90 |             s, unicode) else s
91 |
92 |     range = xrange
   |             ^^^^^^
93 | else:
94 |     to_lldb_str = str
   |

E501 Line too long (116 > 88)
   --> third_party/dawn/src/tint/tint_lldb.py:155:89
    |
153 |     ''''
154 |     get_summary' is annoyingly not a part of the standard LLDB synth provider API.
155 |     This trick allows us to share data extraction logic between synth providers and their sibling summary providers.
    |                                                                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
156 |     '''
157 |     synth = synth_class(valobj.GetNonSyntheticValue(), dict)
    |

UP004 [*] Class `Printer` inherits from `object`
   --> third_party/dawn/src/tint/tint_lldb.py:170:15
    |
170 | class Printer(object):
    |               ^^^^^^
171 |     '''Base class for Printers'''
    |
help: Remove `object` inheritance

B006 Do not use mutable data structures for argument defaults
   --> third_party/dawn/src/tint/tint_lldb.py:173:37
    |
171 |     '''Base class for Printers'''
172 |
173 |     def __init__(self, valobj, dict={}):
    |                                     ^^
174 |         self.valobj = valobj
175 |         self.initialize()
    |
help: Replace with `None`; initialize within function

UP032 [*] Use f-string instead of `format` call
   --> third_party/dawn/src/tint/tint_lldb.py:238:16
    |
237 |       def get_summary(self):
238 |           return 'length={} capacity={}'.format(self.len.GetValueAsUnsigned(),
    |  ________________^
239 | |                                               self.cap.GetValueAsUnsigned())
    | |____________________________________________________________________________^
240 |
241 |       def num_children(self):
    |
help: Convert to f-string

E501 Line too long (106 > 88)
   --> third_party/dawn/src/tint/tint_lldb.py:242:89
    |
241 |     def num_children(self):
242 |         # NOTE: VS Code on MacOS hangs if we try to expand something too large, so put an artificial limit
    |                                                                                         ^^^^^^^^^^^^^^^^^^
243 |         # until we can figure out how to know if this is a valid instance.
244 |         return min(self.len.GetValueAsUnsigned(), 256)
    |

UP031 Use format specifiers instead of percent format
   --> third_party/dawn/src/tint/tint_lldb.py:255:50
    |
253 |             # TODO: return self.value_at(index)
254 |             offset = index * self.elem_size
255 |             return self.data.CreateChildAtOffset('[%s]' % index, offset,
    |                                                  ^^^^^^^^^^^^^^
256 |                                                  self.elem_type)
257 |         except Exception as e:
    |
help: Replace with format specifiers

UP031 Use format specifiers instead of percent format
   --> third_party/dawn/src/tint/tint_lldb.py:264:46
    |
262 |         '''Returns array value at index'''
263 |         offset = index * self.elem_size
264 |         return self.data.CreateChildAtOffset('[%s]' % index, offset,
    |                                              ^^^^^^^^^^^^^^
265 |                                              self.elem_type)
    |
help: Replace with format specifiers

UP032 [*] Use f-string instead of `format` call
   --> third_party/dawn/src/tint/tint_lldb.py:279:16
    |
277 |       def get_summary(self):
278 |           using_heap = self.cap.GetValueAsUnsigned() > self.fixed_size
279 |           return 'heap={} {}'.format(using_heap,
    |  ________________^
280 | |                                    self.slice_printer.get_summary())
    | |____________________________________________________________________^
281 |
282 |       def num_children(self):
    |
help: Convert to f-string

UP032 [*] Use f-string instead of `format` call
   --> third_party/dawn/src/tint/tint_lldb.py:304:16
    |
303 |       def get_summary(self):
304 |           return 'can_move={} {}'.format(self.can_move.GetValue(),
    |  ________________^
305 | |                                        self.slice_printer.get_summary())
    | |________________________________________________________________________^
306 |
307 |       def num_children(self):
    |
help: Convert to f-string

UP032 [*] Use f-string instead of `format` call
   --> third_party/dawn/src/tint/tint_lldb.py:335:16
    |
334 |     def get_summary(self):
335 |         return 'length={}'.format(self.num_children())
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
336 |
337 |     def num_children(self):
    |
help: Convert to f-string

UP032 [*] Use f-string instead of `format` call
   --> third_party/dawn/src/tint/tint_lldb.py:355:43
    |
353 |             kvp = slot, entry
354 |
355 |         return kvp[1].CreateChildAtOffset('[{}]'.format(kvp[0]), 0,
    |                                           ^^^^^^^^^^^^^^^^^^^^^
356 |                                           kvp[1].GetType())
    |
help: Convert to f-string

E722 Do not use bare `except`
   --> third_party/dawn/src/tint/tint_lldb.py:377:9
    |
375 |                 return slot, v
376 |             return None
377 |         except:
    |         ^^^^^^
378 |             return None
    |

E722 Do not use bare `except`
   --> third_party/dawn/src/tint/tint_lldb.py:400:9
    |
398 |                 return k.GetValue(), v
399 |             return None
400 |         except:
    |         ^^^^^^
401 |             return None
    |

W605 [*] Invalid escape sequence: `\[`
  --> third_party/dawn/test/tint/extract-spvasm.py:47:31
   |
45 |     parts = []
46 |     for line in sys.stdin:
47 |         run_match = re.match('\[ RUN\s+\]\s+(\S+)', line)
   |                               ^^
48 |         if run_match:
49 |             test_name = run_match.group(1)
   |
help: Use a raw string literal

W605 [*] Invalid escape sequence: `\s`
  --> third_party/dawn/test/tint/extract-spvasm.py:47:37
   |
45 |     parts = []
46 |     for line in sys.stdin:
47 |         run_match = re.match('\[ RUN\s+\]\s+(\S+)', line)
   |                                     ^^
48 |         if run_match:
49 |             test_name = run_match.group(1)
   |
help: Use a raw string literal

W605 [*] Invalid escape sequence: `\]`
  --> third_party/dawn/test/tint/extract-spvasm.py:47:40
   |
45 |     parts = []
46 |     for line in sys.stdin:
47 |         run_match = re.match('\[ RUN\s+\]\s+(\S+)', line)
   |                                        ^^
48 |         if run_match:
49 |             test_name = run_match.group(1)
   |
help: Use a raw string literal

W605 [*] Invalid escape sequence: `\s`
  --> third_party/dawn/test/tint/extract-spvasm.py:47:42
   |
45 |     parts = []
46 |     for line in sys.stdin:
47 |         run_match = re.match('\[ RUN\s+\]\s+(\S+)', line)
   |                                          ^^
48 |         if run_match:
49 |             test_name = run_match.group(1)
   |
help: Use a raw string literal

W605 [*] Invalid escape sequence: `\S`
  --> third_party/dawn/test/tint/extract-spvasm.py:47:46
   |
45 |     parts = []
46 |     for line in sys.stdin:
47 |         run_match = re.match('\[ RUN\s+\]\s+(\S+)', line)
   |                                              ^^
48 |         if run_match:
49 |             test_name = run_match.group(1)
   |
help: Use a raw string literal

E741 Ambiguous variable name: `l`
  --> third_party/dawn/test/tint/extract-spvasm.py:57:21
   |
55 |             with open(test_name, 'w') as f:
56 |                 f.write('; Test: ' + test_name + '\n')
57 |                 for l in parts:
   |                     ^
58 |                     f.write(l)
59 |                 f.close()
   |

E713 [*] Test for membership should be `not in`
  --> third_party/dawn/test/tint/parse_hlsl_errors.py:50:12
   |
48 | def add_error(error_to_files, error, file):
49 |     error = error.strip()
50 |     if not error in error_to_files:
   |            ^^^^^^^^^^^^^^^^^^^^^^^
51 |         error_to_files[error] = [file]
52 |     else:
   |
help: Convert to `not in`

W605 [*] Invalid escape sequence: `\.`
  --> third_party/dawn/test/tint/parse_hlsl_errors.py:82:30
   |
80 |                 return True
81 |
82 |             m = re.search('.*\.hlsl:[0-9]+:.*?(error.*)', line)  # DXC???
   |                              ^^
83 |             if m:
84 |                 add_error(error_to_files, m.groups()[0], f)
   |
help: Use a raw string literal

UP015 [*] Unnecessary mode argument
   --> third_party/dawn/test/tint/parse_hlsl_errors.py:104:22
    |
102 |     for f in files:
103 |         found_error = False
104 |         with open(f, "r") as fs:
    |                      ^^^
105 |             all_lines = fs.readlines()
106 |             first_line = all_lines[0]
    |
help: Remove mode argument

E501 Line too long (104 > 88)
   --> third_party/dawn/test/tint/parse_hlsl_errors.py:114:89
    |
113 |         if not found_error:
114 |             # If no error message was found, add the SKIP line as it may contain the reason for skipping
    |                                                                                         ^^^^^^^^^^^^^^^^
115 |             add_error(error_to_files, first_line, f)
    |

UP032 [*] Use f-string instead of `format` call
   --> third_party/dawn/test/tint/parse_hlsl_errors.py:122:23
    |
120 |         if args.list_files:
121 |             for f in files:
122 |                 print('\t{}'.format(f))
    |                       ^^^^^^^^^^^^^^^^
    |
help: Convert to f-string

SIM115 Use a context manager for opening files
  --> third_party/dawn/third_party/gn/dxc/build/cmake_configure_file.py:74:11
   |
72 |         return r
73 |
74 |     fin = open(input_file, 'r')
   |           ^^^^
75 |
76 |     output_lines = []
   |

UP015 [*] Unnecessary mode argument
  --> third_party/dawn/third_party/gn/dxc/build/cmake_configure_file.py:74:28
   |
72 |         return r
73 |
74 |     fin = open(input_file, 'r')
   |                            ^^^
75 |
76 |     output_lines = []
   |
help: Remove mode argument

UP015 [*] Unnecessary mode argument
   --> third_party/dawn/third_party/gn/dxc/build/cmake_configure_file.py:133:32
    |
131 |     # Avoid needless incremental rebuilds if the output file exists and hasn't changed
132 |     if os.path.exists(output_file):
133 |         with open(output_file, 'r') as fout:
    |                                ^^^
134 |             if fout.read() == output_text:
135 |                 return 0
    |
help: Remove mode argument

SIM115 Use a context manager for opening files
   --> third_party/dawn/third_party/gn/dxc/build/cmake_configure_file.py:137:12
    |
135 |                 return 0
136 |
137 |     fout = open(output_file, 'w')
    |            ^^^^
138 |     fout.write(output_text)
139 |     return 0
    |

E711 Comparison to `None` should be `cond is None`
  --> third_party/dawn/third_party/gn/dxc/build/message_compiler.py:53:34
   |
51 |     for i, arg in enumerate(rest):
52 |         if arg == '-h' and len(rest) > i + 1:
53 |             assert header_dir == None
   |                                  ^^^^
54 |             header_dir = rest[i + 1]
55 |         elif arg == '-r' and len(rest) > i + 1:
   |
help: Replace with `cond is None`

E711 Comparison to `None` should be `cond is None`
  --> third_party/dawn/third_party/gn/dxc/build/message_compiler.py:56:36
   |
54 |             header_dir = rest[i + 1]
55 |         elif arg == '-r' and len(rest) > i + 1:
56 |             assert resource_dir == None
   |                                    ^^^^
57 |             resource_dir = rest[i + 1]
58 |         elif arg.endswith('.mc') or arg.endswith('.man'):
   |
help: Replace with `cond is None`

E711 Comparison to `None` should be `cond is None`
  --> third_party/dawn/third_party/gn/dxc/build/message_compiler.py:59:34
   |
57 |             resource_dir = rest[i + 1]
58 |         elif arg.endswith('.mc') or arg.endswith('.man'):
59 |             assert input_file == None
   |                                  ^^^^
60 |             input_file = arg
   |
help: Replace with `cond is None`

E501 Line too long (90 > 88)
  --> third_party/dawn/third_party/gn/dxc/build/message_compiler.py:68:89
   |
66 |     source = os.path.join(THIS_DIR, "..", "win_build_output",
67 |                           re.sub(r'.*gn/dxc/', 'mc/', header_dir))
68 |     # If these are new files, create the source directory. The diff will fail later to let
   |                                                                                         ^^
69 |     # the user know what files to copy.
70 |     os.makedirs(source, exist_ok=True)
   |

SIM115 Use a context manager for opening files
  --> third_party/dawn/third_party/gn/dxc/build/message_compiler.py:87:17
   |
85 |     # by CreateProcess. Drop last 2 NULs, one for list terminator, one for
86 |     # trailing vs. separator.
87 |     env_pairs = open(env_file).read()[:-2].split('\0')
   |                 ^^^^
88 |     env_dict = dict([item.split('=', 1) for item in env_pairs])
   |

F841 Local variable `extension` is assigned to but never used
  --> third_party/dawn/third_party/gn/dxc/build/message_compiler.py:90:5
   |
88 |     env_dict = dict([item.split('=', 1) for item in env_pairs])
89 |
90 |     extension = os.path.splitext(input_file)[1]
   |     ^^^^^^^^^
91 |
92 |     # mc writes to stderr, so this explicitly redirects to stdout and eats it.
   |
help: Remove assignment to unused variable `extension`

UP015 [*] Unnecessary mode argument
   --> third_party/dawn/third_party/gn/dxc/build/message_compiler.py:124:36
    |
122 |                 os.path.splitext(os.path.basename(input_file))[0] + '.h')
123 |             header_contents = []
124 |             with open(header_file, 'r') as f:
    |                                    ^^^
125 |                 define_block = []  # The current contiguous block of #defines.
126 |                 for line in f.readlines():
    |
help: Remove mode argument

UP031 Use format specifiers instead of percent format
   --> third_party/dawn/third_party/gn/dxc/build/message_compiler.py:148:19
    |
146 |           diff = filecmp.dircmp(tmp_dir, source)
147 |           if diff.diff_files or set(diff.left_list) != set(diff.right_list):
148 |               print('mc.exe output different from files in %s, see %s' %
    |  ___________________^
149 | |                   (source, tmp_dir))
    | |___________________________________^
150 |               diff.report()
151 |               for f in diff.diff_files:
    |
help: Replace with format specifiers

E701 Multiple statements on one line (colon)
   --> third_party/dawn/third_party/gn/dxc/build/message_compiler.py:152:38
    |
150 |             diff.report()
151 |             for f in diff.diff_files:
152 |                 if f.endswith('.bin'): continue
    |                                      ^
153 |                 fromfile = os.path.join(source, f)
154 |                 tofile = os.path.join(tmp_dir, f)
    |

SIM115 Use a context manager for opening files
   --> third_party/dawn/third_party/gn/dxc/build/message_compiler.py:157:25
    |
155 |                 print(''.join(
156 |                     difflib.unified_diff(
157 |                         open(fromfile).readlines(),
    |                         ^^^^
158 |                         open(tofile).readlines(), fromfile, tofile)))
159 |             delete_tmp_dir = False
    |

SIM115 Use a context manager for opening files
   --> third_party/dawn/third_party/gn/dxc/build/message_compiler.py:158:25
    |
156 |                     difflib.unified_diff(
157 |                         open(fromfile).readlines(),
158 |                         open(tofile).readlines(), fromfile, tofile)))
    |                         ^^^^
159 |             delete_tmp_dir = False
160 |             sys.exit(1)
    |

E501 Line too long (99 > 88)
  --> third_party/dawn/third_party/gn/emsdk/can-use-closure.py:40:89
   |
38 | os.chdir(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
39 |
40 | compiler_path = './third_party/emsdk/upstream/emscripten/node_modules/.bin/google-closure-compiler'
   |                                                                                         ^^^^^^^^^^^
41 | try:
42 |     subprocess.check_call([compiler_path, '--version'],
   |

F401 [*] `os` imported but unused
  --> third_party/dawn/tools/fetch_dawn_dependencies.py:39:8
   |
37 | """
38 |
39 | import os
   |        ^^
40 | import sys
41 | import subprocess
   |
help: Remove unused import: `os`

F401 [*] `sys` imported but unused
  --> third_party/dawn/tools/fetch_dawn_dependencies.py:40:8
   |
39 | import os
40 | import sys
   |        ^^^
41 | import subprocess
42 | import argparse
   |
help: Remove unused import: `sys`

SIM115 Use a context manager for opening files
   --> third_party/dawn/tools/fetch_dawn_dependencies.py:137:12
    |
136 |     log(f"Listing dependencies from {dir_path}")
137 |     DEPS = open(deps_path).read()
    |            ^^^^
138 |
139 |     ldict = {}
    |

E731 Do not assign a `lambda` expression, use a `def`
   --> third_party/dawn/tools/fetch_dawn_dependencies.py:158:9
    |
157 |           # Run git from within the submodule's path (don't use for clone)
158 | /         git = lambda *x: subprocess.run([args.git, '-C', submodule_path, *x],
159 | |                                         capture_output=True)
    | |____________________________________________________________^
160 |
161 |           log(f"Fetching dependency '{submodule}'")
    |
help: Rewrite `git` as a `def`

B023 Function definition does not bind loop variable `submodule_path`
   --> third_party/dawn/tools/fetch_dawn_dependencies.py:158:58
    |
157 |         # Run git from within the submodule's path (don't use for clone)
158 |         git = lambda *x: subprocess.run([args.git, '-C', submodule_path, *x],
    |                                                          ^^^^^^^^^^^^^^
159 |                                         capture_output=True)
    |

E501 Line too long (89 > 88)
   --> third_party/dawn/tools/fetch_dawn_dependencies.py:184:89
    |
182 |         else:
183 |             if args.shallow:
184 |                 log(f"Shallow cloning '{git_url}' at '{git_tag}' into '{submodule_path}'"
    |                                                                                         ^
185 |                     )
186 |                 shallow_clone(git, git_url, git_tag)
    |

E402 Module level import not at top of file
  --> third_party/dawn/tools/run.py:40:1
   |
38 | sys.path.insert(0, DAWN_ROOT)
39 |
40 | from tools.python import cipd_deps
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |

UP031 Use format specifiers instead of percent format
  --> third_party/dawn/webgpu-cts/PRESUBMIT.py:63:50
   |
61 |             cwd=input_api.change.RepositoryRoot())
62 |     except input_api.subprocess.CalledProcessError as e:
63 |         results.append(output_api.PresubmitError('%s' % (e, )))
   |                                                  ^^^^^^^^^^^^
64 |     return results
   |
help: Replace with format specifiers

UP031 Use format specifiers instead of percent format
  --> third_party/dawn/webgpu-cts/scripts/add_query_to_expectation_file.py:63:12
   |
62 | def generate_expectations(queries, tags, results, bug):
63 |     tags = '[ %s ] ' % ' '.join(tags) if tags else ''
   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^
64 |     results = ' [ %s ]' % ' '.join(results)
65 |     bug = bug + ' ' if bug else ''
   |
help: Replace with format specifiers

UP031 Use format specifiers instead of percent format
  --> third_party/dawn/webgpu-cts/scripts/add_query_to_expectation_file.py:64:15
   |
62 | def generate_expectations(queries, tags, results, bug):
63 |     tags = '[ %s ] ' % ' '.join(tags) if tags else ''
64 |     results = ' [ %s ]' % ' '.join(results)
   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
65 |     bug = bug + ' ' if bug else ''
66 |     content = ''
   |
help: Replace with format specifiers

UP032 [*] Use f-string instead of `format` call
  --> third_party/dawn/webgpu-cts/scripts/add_query_to_expectation_file.py:72:24
   |
70 |               logging.warning('Did not get any test names for query %s', q)
71 |           for tn in test_names:
72 |               content += '{bug}{tags}{test}{results}\n'.format(bug=bug,
   |  ________________________^
73 | |                                                              tags=tags,
74 | |                                                              test=tn,
75 | |                                                              results=results)
   | |_____________________________________________________________________________^
76 |       with open(EXPECTATION_FILE_PATH, 'a') as outfile:
77 |           outfile.write(content)
   |
help: Convert to f-string

UP015 [*] Unnecessary mode argument
  --> third_party/dawn/webgpu-cts/scripts/copy_files.py:49:31
   |
47 |     args = parser.parse_args()
48 |
49 |     with open(args.file_list, 'r') as f:
   |                               ^^^
50 |         for file in f.read().splitlines():
51 |             src = os.path.join(args.src_dir, file)
   |
help: Remove mode argument

SIM105 Use `contextlib.suppress(shutil.SameFileError)` instead of `try`-`except`-`pass`
  --> third_party/dawn/webgpu-cts/scripts/copy_files.py:53:13
   |
51 |               src = os.path.join(args.src_dir, file)
52 |               dst = os.path.join(args.dst_dir, file)
53 | /             try:
54 | |                 shutil.copy(src, dst)
55 | |             except shutil.SameFileError:
56 | |                 pass
   | |____________________^
57 |
58 |       if args.stamp:
   |
help: Replace `try`-`except`-`pass` with `with contextlib.suppress(shutil.SameFileError): ...`

F401 [*] `sys` imported but unused
  --> third_party/dawn/webgpu-cts/scripts/gen_ts_dep_lists.py:33:8
   |
31 | import glob
32 | import os
33 | import sys
   |        ^^^
34 |
35 | from dir_paths import gn_webgpu_cts_dir, webgpu_cts_root_dir
   |
help: Remove unused import: `sys`

E501 Line too long (91 > 88)
  --> third_party/dawn/webgpu-cts/scripts/gen_ts_dep_lists.py:45:89
   |
43 |     # "/absolute/path/to/file.ts"
44 |     # The path is always Unix-style.
45 |     # It will also output many Typescript errors since the build doesn't download the .d.ts
   |                                                                                         ^^^
46 |     # dependencies.
47 |     stdout = run_tsc_ignore_errors([
   |

E741 Ambiguous variable name: `l`
  --> third_party/dawn/webgpu-cts/scripts/gen_ts_dep_lists.py:53:29
   |
51 |     ])
52 |
53 |     lines = [l.decode() for l in stdout.splitlines()]
   |                             ^
54 |     return [
55 |         line[len(src_prefix):] for line in lines
   |

UP015 [*] Unnecessary mode argument
  --> third_party/dawn/webgpu-cts/scripts/gen_ts_dep_lists.py:86:35
   |
85 |     if args.check:
86 |         with open(ts_sources_txt, 'r') as f:
   |                                   ^^^
87 |             txt = f.readlines()
88 |             if (txt != ts_sources):
   |
help: Remove mode argument

UP031 Use format specifiers instead of percent format
  --> third_party/dawn/webgpu-cts/scripts/gen_ts_dep_lists.py:90:21
   |
88 |               if (txt != ts_sources):
89 |                   raise RuntimeError(
90 | /                     '%s is out of date. Please re-run //third_party/dawn/webgpu-cts/scripts/gen_ts_dep_lists.py\n'
91 | |                     % ts_sources_txt)
   | |____________________________________^
92 |           with open(resource_files_txt, 'r') as f:
93 |               if (f.readlines() != resource_files):
   |
help: Replace with format specifiers

E501 Line too long (114 > 88)
  --> third_party/dawn/webgpu-cts/scripts/gen_ts_dep_lists.py:90:89
   |
88 |             if (txt != ts_sources):
89 |                 raise RuntimeError(
90 |                     '%s is out of date. Please re-run //third_party/dawn/webgpu-cts/scripts/gen_ts_dep_lists.py\n'
   |                                                                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^
91 |                     % ts_sources_txt)
92 |         with open(resource_files_txt, 'r') as f:
   |

UP015 [*] Unnecessary mode argument
  --> third_party/dawn/webgpu-cts/scripts/gen_ts_dep_lists.py:92:39
   |
90 |                     '%s is out of date. Please re-run //third_party/dawn/webgpu-cts/scripts/gen_ts_dep_lists.py\n'
91 |                     % ts_sources_txt)
92 |         with open(resource_files_txt, 'r') as f:
   |                                       ^^^
93 |             if (f.readlines() != resource_files):
94 |                 raise RuntimeError(
   |
help: Remove mode argument

UP031 Use format specifiers instead of percent format
  --> third_party/dawn/webgpu-cts/scripts/gen_ts_dep_lists.py:95:21
   |
93 |               if (f.readlines() != resource_files):
94 |                   raise RuntimeError(
95 | /                     '%s is out of date. Please re-run //third_party/dawn/webgpu-cts/scripts/gen_ts_dep_lists.py\n'
96 | |                     % resource_files_txt)
   | |________________________________________^
97 |       else:
98 |           with open(ts_sources_txt, 'w') as f:
   |
help: Replace with format specifiers

E501 Line too long (114 > 88)
  --> third_party/dawn/webgpu-cts/scripts/gen_ts_dep_lists.py:95:89
   |
93 |             if (f.readlines() != resource_files):
94 |                 raise RuntimeError(
95 |                     '%s is out of date. Please re-run //third_party/dawn/webgpu-cts/scripts/gen_ts_dep_lists.py\n'
   |                                                                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^
96 |                     % resource_files_txt)
97 |     else:
   |

E501 Line too long (95 > 88)
  --> third_party/dawn/webgpu-cts/scripts/tsc_ignore_errors.py:56:89
   |
54 |     stdout, stderr = process.communicate()
55 |
56 |     # Typecheck errors go in stdout, not stderr. If we see something in stderr, raise an error.
   |                                                                                         ^^^^^^^
57 |     if len(stderr):
58 |         raise RuntimeError('tsc \'%s\' failed\n%s' % (' '.join(cmd), stderr))
   |

UP031 Use format specifiers instead of percent format
  --> third_party/dawn/webgpu-cts/scripts/tsc_ignore_errors.py:58:28
   |
56 |     # Typecheck errors go in stdout, not stderr. If we see something in stderr, raise an error.
57 |     if len(stderr):
58 |         raise RuntimeError('tsc \'%s\' failed\n%s' % (' '.join(cmd), stderr))
   |                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
59 |
60 |     return stdout
   |
help: Replace with format specifiers

Found 344 errors.
[*] 95 fixable with the `--fix` option (74 hidden fixes can be enabled with the `--unsafe-fixes` option).
root@648a77abe1dc:/usr/src/app#

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 12, 2026

@Ryan-Millard Ryan-Millard left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thank you for the PR @Geff115!

These are just some simple changes that I'd like you to implement:

  1. Reverting the changes to the img2num Bash script (you can use git checkout main -- img2num if you'd like).
  2. Add a quick comment to the root pyproject.toml for my future sanity.😁

There's also a linting problem related to a .editorconfig violation, but it's minor and I can just fix it before merging.

Other than that, this is wonderful and you actually do not need to add anything further. Thanks again!

Comment thread img2num Outdated
Comment thread img2num Outdated
Comment thread img2num Outdated
Comment thread pyproject.toml
@Krasner

Krasner commented May 12, 2026

Copy link
Copy Markdown
Collaborator

@Krasner, after having some time to think about it, I think another monorepo management tool would be better because we already have a lot of workspace-level pnpm scripts for the entire project that are dedicated to JavaScript. By adding more scripts, we'd make the package.json files large and harder to manage. I think a separation of concerns might be slightly cleaner.

I'll open a discussion about this so we can avoid the off-topic conversation on this PR.

@Geff115 we don't actually need the long uv run --extra dev ruff check packages/py example-apps command because we can just install the dev extras with uv and run ruff normally (see below). For this, we'll just need to install it into the Dockerfile.dev image so contributors don't have to worry about it at all. I need to fix the Python dependency setup in the Dockerfile.dev, so I'll also add that.

The ruff command can be simplified

@Krasner, after having some time to think about it, I think another monorepo management tool would be better because we already have a lot of workspace-level pnpm scripts for the entire project that are dedicated to JavaScript. By adding more scripts, we'd make the package.json files large and harder to manage. I think a separation of concerns might be slightly cleaner.

I'll open a discussion about this so we can avoid the off-topic conversation on this PR.

@Geff115 we don't actually need the long uv run --extra dev ruff check packages/py example-apps command because we can just install the dev extras with uv and run ruff normally (see below). For this, we'll just need to install it into the Dockerfile.dev image so contributors don't have to worry about it at all. I need to fix the Python dependency setup in the Dockerfile.dev, so I'll also add that.

The ruff command can be simplified

@Ryan-Millard using ruff check . looks through all python files including those in the third_party packages. We should not do this. It only needs to check packages/py and example_apps/

@Ryan-Millard

Ryan-Millard commented May 12, 2026

Copy link
Copy Markdown
Owner

@Ryan-Millard using ruff check . looks through all python files including those in the third_party packages. We should not do this. It only needs to check packages/py and example_apps/

That's a great catch! Shouldn't we rather add that piece to the ignore config section for ruff? That way, it'll still lint the Python files in core/tools and other places, too.

ignore = [ "third_party" ]

If you agree, please leave a review and ask for that change to be added.

@Ryan-Millard

Copy link
Copy Markdown
Owner

For the monorepo management system, please see discussion #376

Comment thread pyproject.toml
… on the suggested changes from Ryan and Krasner
@coderabbitai coderabbitai Bot added ci example-app Updates to code related to demonstration (example) applications build-system python changes to python bindings or python code labels May 13, 2026
@Geff115

Geff115 commented May 13, 2026

Copy link
Copy Markdown
Contributor Author

Hi @Ryan-Millard @Krasner,

I have resolved the img2num script and the pyproject.toml file based on the suggested changes aforementioned. Let me know if you'd like me to pick up anything else.

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 13, 2026
@Ryan-Millard

Copy link
Copy Markdown
Owner

Hi @Ryan-Millard @Krasner,

I have resolved the img2num script and the pyproject.toml file based on the suggested changes aforementioned. Let me know if you'd like me to pick up anything else.

Hi @Geff115. Sorry for the late reply - I've had a lot of work recently and only had time to spare now.

This is great stuff - thank you! I'll just wait for the workflows to complete and merge it.

@Ryan-Millard Ryan-Millard changed the title feat(python): set up Ruff linting for Python bindings feat(packages/py): set up Ruff linting for Python bindings May 16, 2026
@Ryan-Millard
Ryan-Millard merged commit d6cc972 into Ryan-Millard:main May 16, 2026
11 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build-system ci documentation example-app Updates to code related to demonstration (example) applications python changes to python bindings or python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore(python): set up Python linting via Ruff and update .coderabbit.yaml

3 participants