pugjs / pug

Pug – robust, elegant, feature rich template engine for Node.js
https://pugjs.org
21.64k stars 1.96k forks source link

Parsing problems inside of parens when attempting to choose a style for a table row #3377

Open pszabop opened 2 years ago

pszabop commented 2 years ago

Pug Version: 3.02

Node Version: 12.16.3

Input JavaScript Values

pug.renderFile('input.pug', {
  JSON.parse('{ "indicators": [{"quality":"insufficientData","concerns":[],"signalsToView":[],"category":"BLOOD_GLUCOSE"},{"quality":"insufficientData","concerns":[{"message":"You have not recorded enough data for us to evaluate your exercise recovery.","briefHelp":"We cannot accurately evaluate your exercise recovery until you record exercise activity, daily stress, deep sleep, sleeping respiration, and sleeping heart rate data.","helpUrl":"healthIndicatorsHelp.html#insufficientDataToEvaluateExerciseRecovery"}],"signalsToView":[],"category":"EXERCISE_RECOVERY"},{"quality":"ok","concerns":[{"message":"Your sleeping heart rate ripple is too large on one or more nights.","briefHelp":"The amount of noise present in your sleeping heart rate is too large. This may be an indication of a cardiac arrhythmia and you should immediately consult with your physician or cardiologist.","helpUrl":"healthIndicatorsHelp.html#meanSleepingHeartRateRippleViolatesMax"},{"message":"Your sleeping heart rate pattern is not consistently ideal.","briefHelp":"The quality of your sleep is less than ideal. This may be caused by going to bed at the wrong time, eating too many carbs or sugars before going to bed, or being too physically exhausted.","helpUrl":"healthIndicatorsHelp.html#sleepingHeartRateNotIdealMajorityOfTime"}],"signalsToView":["heartRateSleepRipple","heartRateSleepPattern"],"category":"HEART_RATE"},{"quality":"insufficientData","concerns":[],"signalsToView":[],"category":"NUTRITION"},{"quality":"excellent","concerns":[],"signalsToView":[],"category":"PULSE_OXIMETRY"},{"quality":"excellent","concerns":[],"signalsToView":[],"category":"SLEEP"},{"quality":"excellent","concerns":[],"signalsToView":[],"category":"STRESS"}] }
}');

Input Pug

doctype html
html
    head
        title One | Health Indicators
        link(rel="stylesheet", href="../../../node_modules/bootstrap/dist/css/bootstrap.css")

    body
        p Your v1 Health Indicators
        center
        table(class='table table-striped')
          thead
            tr
              th Name
              th Quality
              th Message
              th Signals to View
          tbody
            each indicator in indicators
              tr( 
                  class
                    case indicator.quality
                      when "insufficientData" ="table table-info"
                      when "ok" ="table table-success"
                      default ="table table-default"
                )
                td  #{indicator.category}
                td  #{indicator.quality}
                td
                  each concern in indicator.concerns ? indicator.concerns: []  
                    p #{concern.message}
                td #{indicator.signalsToView}

Expected HTML

(not expecting an error)

Actual HTML

error

PluginError: /home/node/app/src/emails/health-indicators/health-indicators.pug:23:23
    21|                     case indicator.quality
    22|                       when "insufficientData" ="table table-info"
  > 23|                       when "ok" ="table table-success"
------------------------------^
    24|                       default ="table table-default"
    25|                 )
    26|                 td  #{indicator.category}

Duplicate attribute "when" is not allowed.

Additional Comments

When there is only one when statement the code behaves correctly: i.e. this doesn't fail, and it renders the correct style choice as well:

              tr( 
                  class
                    case indicator.quality
                      when "insufficientData" ="table table-info"
                      default ="table table-default"
                )

There's something funny going on with the parser inside those table row (tr) parenthesis, as I get unclosed brace errors if I use an if statement instead of a case statement and adding a legal : to the when statement also causes an INVALID_KEY_CHARACTER, yet there are plenty of examples of these working outside this context

pszabop commented 2 years ago

Here's a standalone example:

doctype html
html    
    head
        title One | table rows and case statements
        link(rel="stylesheet", href="../../../node_modules/bootstrap/dist/css/bootstrap.css")

    body
      // this works
      - var friends = [0, 1, 2, 3, 4, 5]
      case friends[1]
        when 0
          p you have no friends
        when 1
          p you have a friend
        default
          p you have #{friends} friends

      // this works when there's no per-row class
      table(class='table table-striped')
        thead         
          tr          
            th Friend Count
            th Correct English Phrase
        tbody   
          each friend in friends
            tr  
              td #{friend}
              case friend
                when 0
                  td you have no friends
                when 1
                  td you have a friend
                default
                  td you have #{friend} friends

      // this throws errors when there's a per-row-class
      table(class='table table-striped')
        thead
          tr 
            th Friend Count
            th Correct English Phrase
        tbody
          each friend in friends
            tr(
              class
              case friend
                when 0 ="table table-warning"
                when 1 ="table table-success"
                default ="table table-warning"
            )   
              td #{friend}
              case friend
                when 0  
                  td you have no friends
                when 1
                  td you have a friend
                default
                  td you have #{friend} friends

that generates this error:

PluginError: /home/node/app/src/emails/one/one.template.pug:48:17
    46|               case friend
    47|                 when 0 ="table table-warning"
  > 48|                 when 1 ="table table-success"
------------------------^
    49|                 default ="table table-warning"
    50|             )
    51|               td #{friend}

Duplicate attribute "when" is not allowed.
webdiscus commented 2 years ago

@pszabop

you use Pug statements wrong:

tr(
   class
     case friend
       when 0 ="table table-warning"
       when 1 ="table table-success"
       default ="table table-warning"
)

The case can not be used in a Tag attribute.

Solution: any complex logic use in JavaScript context and in Tag attributes use a variable only.

Define an object with all variants of statments:

-
    const classVariants = {
      0: "table table-warning", // at 0 is your default value
      1: "table table-success",
    }

then use the variable in Tag attribute:

-
    let friend = 1; // <- HERE is your variable
    let className = classVariants[friend] || classVariants[0];

div(class=className) TEST
pszabop commented 2 years ago

The case can not be used in a Tag attribute.

Is this documented anywhere? I saw some one-liners for selecting tag attributes in the documentation. Is there a limit to how much code can be used inside a tag attribute?

This is the example in the documentation, which is where I started:

- var authenticated = true
body(class=authenticated ? 'authed' : 'anon')

The error I got did not match this restriction, and in fact worked partially, just not generally.

trasherdk commented 2 years ago

This:

- var authenticated = true
body(class=authenticated ? 'authed' : 'anon')

is identical to:

-
    let friend = 1; // <- HERE is your variable
    let className = classVariants[friend] || classVariants[0];

div(class=className) TEST

I'm guessing you are looking for something like:

    each indicator in indicators
      tr( 
        case indicator.quality
          when "insufficientData" 
            class "table table-info"
          when "ok" 
            class "table table-success"
          default 
            class "table table-default"
        )
webdiscus commented 2 years ago

This:

- var authenticated = true
body(class=authenticated ? 'authed' : 'anon')

is identical to:

-
    let friend = 1; // <- HERE is your variable
    let className = classVariants[friend] || classVariants[0];

div(class=className) TEST

I wrote a common solution for many cases:

-
    const classVariants = {
      0: "table table-warning", // at 0 is your default value
      1: "table table-success",
      2: "table table-info",
      3: "table table-error",
      // ...
    }

    let variant = 2 // 0..n
    let className = classVariants[variant] || classVariants[0];

div(class=className) TEST
pszabop commented 2 years ago

Those solutions all work.

This is a feature request to update the documentation with one of the above examples.

thanks!