Skip to content

Expose error and warning locations as data, not only in the message #18

Closed

Description

@malomalo

Summary

LightningCSS::ParseError and Result#warnings only give a message string, with the location appended as at file:line:column. A tool that wants to point at the source (an asset pipeline building a backtrace, an editor integration, a linter) has to pull the location out of the message with a regex. Lightning CSS's Node binding returns the location and the error type as separate fields.

Current behaviour

lightningcss 1.33.0.1 (crate 1.0.0-alpha.72), Ruby 4.0.5, arm64-darwin

css = ".a { color: red; }\n.b, { color: blue; }\n"

begin
  LightningCSS.transform(css, filename: "main.css")
rescue LightningCSS::ParseError => e
  e.message                                 #=> "Invalid empty selector at main.css:1:5"
  e.methods - StandardError.instance_methods #=> []
end

LightningCSS.transform(css, filename: "main.css", error_recovery: true).warnings
#=> ["Invalid empty selector at main.css:1:5"]

The same input through the npm package (1.33.0):

// thrown error
e.name      // "SyntaxError"
e.message   // "Invalid empty selector"
e.fileName  // "main.css"
e.loc       // { line: 2, column: 5 }

// with errorRecovery: true
warnings    // [{ message: "Invalid empty selector", type: "SelectorError", value: { type: "EmptySelector" }, loc: { filename: "main.css", line: 2, column: 5 } }]

Cause

rust/src/lib.rs (on main, aa4ee79) turns errors into a string with Failure::parse(error.to_string()) (e.g. lines 165 and 237), and warnings with warning.to_string() (e.g. line 206). The C extension then raises the error class with only that string. The location and kind are thrown away at the FFI boundary.

Suggested API

Keep the current messages, so rescue => e; e.message and warnings.first keep working, and add the fields:

rescue LightningCSS::ParseError => e
  e.message   #=> "Invalid empty selector at main.css:2:5"
  e.reason    #=> "Invalid empty selector"   # message without the location
  e.filename  #=> "main.css"
  e.line      #=> 2                          # 1-based, see #<0-based lines issue>
  e.column    #=> 5
end

warning = result.warnings.first   # LightningCSS::Warning
warning.to_s      #=> "Invalid empty selector at main.css:2:5" (as today)
warning.message   #=> "Invalid empty selector"
warning.type      #=> "SelectorError"
warning.filename  #=> "main.css"
warning.line      #=> 2
warning.column    #=> 5

The FFI could return failures as JSON like results already are ({"message", "type", "loc": {...}}), with the C extension or lib/ building the exception from it. BundleError would also benefit from filename, since a bundle error can come from any imported file.

Making warnings an array of Warning objects instead of strings would change its type. Keeping to_s, and possibly == against a String, would keep code that treats warnings as strings working.

This overlaps with #<0-based lines issue>. The two could be fixed together, but they're filed separately because that one changes existing output and this one adds API.

Activity

  1. marcoroth commented on Oct 14, 2026

    @marcoroth
    Owner

    Thanks @malomalo, this is now resolved via #22

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions