Skip to content

Conversation

reduckted
Copy link

Which issue, if any, is this issue related to?

Closes #8695

Is there anything in the PR that needs further explanation?

I couldn't find any other rules that specifically check for the source coming from a style attribute, so I don't know if this is the preferred solution.

For reference, postcss-html sets inline: true and lang: "css" when the CSS comes from a style attribute:

Copy link

changeset-bot bot commented Jul 30, 2025

🦋 Changeset detected

Latest commit: 299318f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
stylelint Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@jeddy3 jeddy3 changed the title Do not check no-invalid-position-declaration in HTML style attribute. Fix no-invalid-position-declaration false positives for embedded styles Jul 30, 2025
@@ -316,3 +316,29 @@ testRule({
},
],
});

Copy link
Contributor

@sw1tch3roo sw1tch3roo Jul 30, 2025

Choose a reason for hiding this comment

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

maybe after adding the main logic, add tests with the css-in-js example that @jeddy3 mentioned in the issue?

I mean

const foo = styled.a`color: red;`

@jeddy3
Copy link
Member

jeddy3 commented Jul 30, 2025

@reduckted Thank you for making a start on this.

I couldn't find any other rules that specifically check for the source coming from a style attribute, so I don't know if this is the preferred solution.

Thanks for looking. We've previously removed syntax-specific code from our rules, especially when it accesses something that's not part of their public API.

Custom syntaxes that extract embedded code use Document. We could check for that instead it's part of PostCSS public API.

We do this already in one of our utils:

/**
* @param {import('postcss').Node} node
* @returns {boolean}
*/
function isInDocument({ parent }) {
return Boolean(parent && isDocument(parent));
}

The postcss-html AST:

jeddy3@mac % node -e 'require("postcss-html").parse(`<a style="color: red;"></a>`).walkDecls(console.log)'         

<ref *1> Declaration {
  raws: { before: '', between: ': ' },
  type: 'decl',
  parent: Root {
    raws: {
      semicolon: true,
      after: '',
      codeBefore: '<a style="',
      codeAfter: '"></a>'
    },
    type: 'root',
    nodes: [ [Circular *1] ],
    source: {
      input: [Input],
      start: [Object],
      end: [Object],
      inline: true,
      lang: 'css',
      syntax: [Object]
    },
    lastEach: 2,
    indexes: { '2': 0 },
    document: Document {
      raws: {},
      type: 'document',
      nodes: [Array],
      source: [Object],
      Symbol(isClean): false,
      Symbol(my): true
    },
    Symbol(isClean): false,
    Symbol(my): true
  },
  source: {
    input: Input {
      css: 'color: red;',
      hasBOM: false,
      document: '<a style="color: red;"></a>',
      id: '<input css zeyKSU>',
      Symbol(lineToIndexCache): [Array]
    },
    start: { column: 11, line: 1, offset: 10 },
    end: { column: 21, line: 1, offset: 21 }
  },
  prop: 'color',
  value: 'red',
  Symbol(isClean): false,
  Symbol(my): true
}

Compared to the default postcss parser:

jeddy3@mac % node -e 'require("postcss").parse(`color: red;`).walkDecls(console.log)'

<ref *1> Declaration {
  raws: { before: '', between: ': ' },
  type: 'decl',
  parent: Root {
    raws: { semicolon: true, after: '' },
    type: 'root',
    nodes: [ [Circular *1] ],
    source: { input: [Input], start: [Object], end: [Object] },
    lastEach: 1,
    indexes: { '1': 0 },
    Symbol(isClean): false,
    Symbol(my): true
  },
  source: {
    input: Input {
      css: 'color: red;',
      hasBOM: false,
      document: 'color: red;',
      id: '<input css 5sX5ub>',
      Symbol(lineToIndexCache): [Array]
    },
    start: { column: 1, line: 1, offset: 0 },
    end: { column: 11, line: 1, offset: 11 }
  },
  prop: 'color',
  value: 'red',
  Symbol(isClean): false,
  Symbol(my): true
}

This should also fix the false positives styled css-in-js due to its AST:

jeddy3@mac  % node -e 'require("postcss-styled-syntax").parse("const foo = styled.a`color: red;`").walkDecls(console.log)'

<ref *1> Declaration {
  raws: { before: '', between: ': ' },
  type: 'decl',
  parent: Root {
    raws: {
      styledSyntaxRangeStart: 21,
      styledSyntaxRangeEnd: 32,
      isRuleLike: true,
      styledSyntaxIsComponent: true,
      semicolon: true,
      after: '',
      codeBefore: 'const foo = styled.a`',
      codeAfter: '`'
    },
    type: 'root',
    nodes: [ [Circular *1] ],
    source: { input: [Input], start: [Object], end: [Object] },
    parent: Document {
      raws: {},
      type: 'document',
      source: [Object],
      nodes: [Array],
      lastEach: 1,
      indexes: [Object],
      Symbol(isClean): false,
      Symbol(my): true
    },
    lastEach: 1,
    indexes: { '1': 0 },
    Symbol(isClean): false,
    Symbol(my): true
  },
  source: {
    input: Input {
      css: 'color: red;',
      hasBOM: false,
      document: 'const foo = styled.a`color: red;`',
      id: '<input css VgzK4F>',
      Symbol(lineToIndexCache): [Array]
    },
    start: { offset: 21, line: 1, column: 22 },
    end: { offset: 31, line: 1, column: 32 }
  },
  prop: 'color',
  value: 'red',
  Symbol(isClean): false,
  Symbol(my): true
}

However, this approach will introduce false negatives for embedded <style> blocks:

<style>
  color: red;
</style>

But we typically prefer false negatives to false positives as they're less disruptive to users.

If we go this route, let's open up a follow-up issue to track the false negatives. It'll likely be part of a broader effort to consistently handle embedded styles (see #6833).

@@ -23,6 +23,18 @@ const rule = (primary) => {

if (!validOptions) return;

if (
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's better to move this condition before validOptions assignment to avoid unnecessary operations

@reduckted
Copy link
Author

Custom syntaxes that extract embedded code use Document.

isDocument only checks that the node's type is document, which is only true for the postcss-styled-syntax AST.

@reduckted
Copy link
Author

We've previously removed syntax-specific code from our rules, especially when it accesses something that's not part of their public API.

Completely understandable. Unfortunately, I can't see a way to detect this using only the public API.

The only sure-fire way to prevent false positives is to disable the rule in your config for files with embedded styles.

@sw1tch3roo
Copy link
Contributor

@reduckted hi, let me try to research this question

@reduckted
Copy link
Author

Closing in favor of #8701.

@reduckted reduckted closed this Aug 2, 2025
@reduckted reduckted deleted the fix/no-invalid-position-declaration-in-style-attribute branch August 4, 2025 10:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Fix no-invalid-position-declaration false positives for embedded styles
3 participants