Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

context as wrong fix value if there is any stylelint-disable-next line in it #7547

Open
sonikasharma1403 opened this issue Mar 8, 2024 · 10 comments
Labels
status: needs reproducible example triage needs reproducible demo or repo

Comments

@sonikasharma1403
Copy link

sonikasharma1403 commented Mar 8, 2024

What minimal example or steps are needed to reproduce the bug?

  1. create a custom plugin..
  2. log the value of context.fix
  3. create 2 issues in your css/scss file
  4. disable one issue using /* stylelint-disable-next-line plugin-name */
  5. notice that the issue should be reported for the 2nd issue added.. instead the plugin is getting context.fix as false.

What minimal configuration is needed to reproduce the bug?

{
"plugins": [
    "stylelint-scss",
    "./libs/assets/src/lib/scripts/stylelint-rule-no-deprecated-variables.js"
  ],
  "rules": {
    "my-plugin/stylelint-rule-no-deprecated-variables": true,
    }
}

simple plugin and check value of context.fix

How did you run Stylelint?

npm run lint:scss -- --fix --skip-nx-cache

Which Stylelint-related dependencies are you using?

"stylelint": "^15.10.3",
"stylelint-config-idiomatic-order": "^8.1.0",
"stylelint-config-standard-scss": "^11.0.0",
"stylelint-scss": "^5.2.1",

What did you expect to happen?

context.fix should be true depending on the flag. and should not be dependent on some comment

What actually happened?

as mentioned above

Do you have a proposal to fix the bug?

No response

@Mouvedia Mouvedia added the status: needs discussion triage needs further discussion label Mar 8, 2024
@ybiquitous
Copy link
Member

@sonikasharma1403 Thanks for the report. Can you provide a minimal reproducible repository so that we can investigate this problem?

@ybiquitous ybiquitous added status: needs reproducible example triage needs reproducible demo or repo and removed status: needs discussion triage needs further discussion labels Mar 8, 2024
@sonikasharma1403
Copy link
Author

sonikasharma1403 commented Mar 11, 2024

.stylelintrc.json:

  "extends": ["stylelint-config-standard-scss", "stylelint-config-idiomatic-order"],
  "plugins": [
    "stylelint-scss",
    "./libs/assets/src/lib/scripts/stylelint-rule-no-deprecated-variables.js"
  ],
  "rules": {
    "@plugin/no-deprecated-variables": [true]
    ...
    }
}


`stylelint-rule-no-deprecated-variables.js`:
`
const stylelint = require('stylelint');
const fs = require('fs');
const path = require('path');
const postcss = require('postcss');

const { report, ruleMessages, validateOptions } = stylelint.utils;

const ruleName = '@plugin/no-deprecated-variables';
const deprecatedFilePath = './stylelint-rule-no-deprecated-variables.json';

const readDeprecatedValues = (filePath) => {
  try {
    const fileContent = fs.readFileSync(path.join(__dirname, filePath), 'utf8');
    return JSON.parse(fileContent);
  } catch (error) {
    console.error(`Error reading deprecated values file: ${error.message}`);
    return { deprecatedWithReplacement: [], deprecated: [], themeReplacement: [] };
  }
};

const { deprecatedWithReplacement, deprecated, themeReplacement } = readDeprecatedValues(deprecatedFilePath);

const messages = ruleMessages(ruleName, {
  expected: (unfixed, fixed, warning) =>
    warning
      ? `Verify: ${unfixed || ''} ${fixed ? `(${fixed || ''})` : ''} is used within a function`
      : fixed
      ? `Expected "${unfixed}" to be "${fixed}"`
      : `"${unfixed}" has been deprecated, use appropriate lego variables`,
});

module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOption, secondaryOptionObject, context) {
  return function lint(postcssRoot, postcssResult) {
    const options = {
      severity: 'error',
      dir: '',
      autofix: false,
      ...secondaryOptionObject,
    };

    const validOptions = validateOptions(postcssResult, ruleName, {
      actual: options,
      possible: {
        severity: ['error', 'warning', 'off'],
        autofix: [true, false],
        dir: (value) => typeof value === 'string',
      },
      optional: true,
    });

    if (!validOptions) {
      return;
    }

    if (!options.severity) {
      return;
    }

    const filePath = postcssResult.opts.from;
    if (!(filePath && filePath.includes(options.dir))) {
      return;
    }

    const isAutoFixing = Boolean(context.fix);
    const issues = [];

    const processNode = (node, params, isRule, isProp, issues, replacements) => {
      replacements.forEach((item) => {
        const regex = new RegExp(`\\${item.from}\\b(?!-)`, 'g');
        const functionFromRegex = new RegExp(`\\${item.from}\\b(?=[^(!-]*\\))`, 'g');

        if (regex.test(params)) {
          const entry = {
            [isRule ? 'rule' : 'decl']: node,
            from: item.from,
            to: item.to,
          };

          if (isProp) {
            entry.replaceProp = true;
          }

          if (functionFromRegex.test(params) && !node.name) {
            entry.showWarning = true;
          }

          issues.push(entry);
        }
      });
    };

    postcssRoot.walkDecls((decl) => {
      processNode(decl, decl.value, false, false, issues, deprecatedWithReplacement.concat(deprecated));
    });

    postcssRoot.walkAtRules((rule) => {
      processNode(rule, rule.params, true, false, issues, deprecatedWithReplacement.concat(deprecated));
    });

    if (isAutoFixing) {
      issues.forEach((replacement) => {
        const { decl, from, to, replaceProp, rule, showWarning, hasChanged } = replacement;
        if (to && !hasChanged) {
          const newValue = (rule ? rule.params : replaceProp ? decl.prop : decl.value).replace(
            new RegExp(`\\${from}\\b(?!-)`, 'g'),
            to
          );
          if (replaceProp) {
            const newRule = postcss.rule({ selector: ':root', source: postcssRoot.source });
            newRule.append(postcss.decl({ prop: newValue, value: decl.value, source: postcssRoot.source }));
            decl.remove();
            postcssRoot.prepend(newRule);
          } else {
            if (rule && rule.params) {
              rule.params = newValue;
            } else {
              if (decl.raws.value) {
                decl.raws.value.raw = newValue;
              } else {
                decl.value = newValue;
              }
            }
          }

          if (showWarning) {
            const message = to ? messages.expected(from, to, showWarning) : messages.expected(from, false, showWarning);
            report({
              ruleName,
              result: postcssResult,
              message,
              node: decl || rule,
              word: from,
              severity: options.severity,
            });
          }
        }
      });
    } else {
      issues.forEach((issue) => {
        const { decl, from, to, rule, showWarning, hasChanged } = issue;
        const message = to ? messages.expected(from, to) : messages.expected(from);
        if (!hasChanged) {
          report({
            ruleName,
            result: postcssResult,
            message,
            node: decl || rule,
            word: from,
            severity: options.severity,
          });
        }

        if (showWarning) {
          const message = to ? messages.expected(from, to, showWarning) : messages.expected(from, false, showWarning);

          report({
            ruleName,
            result: postcssResult,
            message,
            node: decl || rule,
            word: from,
            severity: options.severity,
          });
        }
      });
    }
  };
});

module.exports.ruleName = ruleName;
module.exports.messages = messages;

`

@sonikasharma1403
Copy link
Author

sample json { "deprecatedWithReplacement": [ { "from": "$plugin-primary-lightest", "to": "$new-color-primary-subtlest" }, ... ] }

create a scss file
use the deprecated variable

.test { color: $plugin-primary-lightest} .bgTest: { background-color: $plugin-primary-lightest}

notice that now the error is reported for both the instances of $plugin-primary-lightest. disable one error with stylelint-disable-next-line @plugin/no-deprecated-variables. Rerun the stylelint script - notice that for this file, the context.fix is false

@sonikasharma1403
Copy link
Author

@ybiquitous - any update?

@ybiquitous
Copy link
Member

#7547 (comment) seems to have broken syntax highlighting. Can you repair it, please?

@sonikasharma1403
Copy link
Author

sonikasharma1403 commented Apr 3, 2024

sure

.stylelintrc.json

{
  "extends": ["stylelint-config-standard-scss", "stylelint-config-idiomatic-order"],
  "plugins": [
    "stylelint-scss",
    "./libs/assets/src/lib/scripts/stylelint-rule-no-deprecated-variables.js"
  ],
  "rules": {
    "@plugin/no-deprecated-variables": [true]
  }
}
const stylelint = require('stylelint');
const fs = require('fs');
const path = require('path');
const postcss = require('postcss');

const { report, ruleMessages, validateOptions } = stylelint.utils;

const ruleName = '@plugin/no-deprecated-variables';
const deprecatedFilePath = './stylelint-rule-no-deprecated-variables.json';

const readDeprecatedValues = (filePath) => {
  try {
    const fileContent = fs.readFileSync(path.join(__dirname, filePath), 'utf8');
    return JSON.parse(fileContent);
  } catch (error) {
    console.error(`Error reading deprecated values file: ${error.message}`);
    return { deprecatedWithReplacement: [], deprecated: [], themeReplacement: [] };
  }
};

const { deprecatedWithReplacement, deprecated, themeReplacement } = readDeprecatedValues(deprecatedFilePath);

const messages = ruleMessages(ruleName, {
  expected: (unfixed, fixed, warning) =>
    warning
      ? `Verify: ${unfixed || ''} ${fixed ? `(${fixed || ''})` : ''} is used within a function`
      : fixed
      ? `Expected "${unfixed}" to be "${fixed}"`
      : `"${unfixed}" has been deprecated, use appropriate lego variables`,
});

module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOption, secondaryOptionObject, context) {
  return function lint(postcssRoot, postcssResult) {
    const options = {
      severity: 'error',
      dir: '',
      autofix: false,
      ...secondaryOptionObject,
    };

    const validOptions = validateOptions(postcssResult, ruleName, {
      actual: options,
      possible: {
        severity: ['error', 'warning', 'off'],
        autofix: [true, false],
        dir: (value) => typeof value === 'string',
      },
      optional: true,
    });

    if (!validOptions) {
      return;
    }

    if (!options.severity) {
      return;
    }

    const filePath = postcssResult.opts.from;
    if (!(filePath && filePath.includes(options.dir))) {
      return;
    }

    const isAutoFixing = Boolean(context.fix);
    const issues = [];

    const processNode = (node, params, isRule, isProp, issues, replacements) => {
      replacements.forEach((item) => {
        const regex = new RegExp(`\\${item.from}\\b(?!-)`, 'g');
        const functionFromRegex = new RegExp(`\\${item.from}\\b(?=[^(!-]*\\))`, 'g');

        if (regex.test(params)) {
          const entry = {
            [isRule ? 'rule' : 'decl']: node,
            from: item.from,
            to: item.to,
          };

          if (isProp) {
            entry.replaceProp = true;
          }

          if (functionFromRegex.test(params) && !node.name) {
            entry.showWarning = true;
          }

          issues.push(entry);
        }
      });
    };

    postcssRoot.walkDecls((decl) => {
      processNode(decl, decl.value, false, false, issues, deprecatedWithReplacement.concat(deprecated));
    });

    postcssRoot.walkAtRules((rule) => {
      processNode(rule, rule.params, true, false, issues, deprecatedWithReplacement.concat(deprecated));
    });

    if (isAutoFixing) {
      issues.forEach((replacement) => {
        const { decl, from, to, replaceProp, rule, showWarning, hasChanged } = replacement;
        if (to && !hasChanged) {
          const newValue = (rule ? rule.params : replaceProp ? decl.prop : decl.value).replace(
            new RegExp(`\\${from}\\b(?!-)`, 'g'),
            to
          );
          if (replaceProp) {
            const newRule = postcss.rule({ selector: ':root', source: postcssRoot.source });
            newRule.append(postcss.decl({ prop: newValue, value: decl.value, source: postcssRoot.source }));
            decl.remove();
            postcssRoot.prepend(newRule);
          } else {
            if (rule && rule.params) {
              rule.params = newValue;
            } else {
              if (decl.raws.value) {
                decl.raws.value.raw = newValue;
              } else {
                decl.value = newValue;
              }
            }
          }

          if (showWarning) {
            const message = to ? messages.expected(from, to, showWarning) : messages.expected(from, false, showWarning);
            report({
              ruleName,
              result: postcssResult,
              message,
              node: decl || rule,
              word: from,
              severity: options.severity,
            });
          }
        }
      });
    } else {
      issues.forEach((issue) => {
        const { decl, from, to, rule, showWarning, hasChanged } = issue;
        const message = to ? messages.expected(from, to) : messages.expected(from);
        if (!hasChanged) {
          report({
            ruleName,
            result: postcssResult,
            message,
            node: decl || rule,
            word: from,
            severity: options.severity,
          });
        }

        if (showWarning) {
          const message = to ? messages.expected(from, to, showWarning) : messages.expected(from, false, showWarning);

          report({
            ruleName,
            result: postcssResult,
            message,
            node: decl || rule,
            word: from,
            severity: options.severity,
          });
        }
      });
    }
  };
});

module.exports.ruleName = ruleName;
module.exports.messages = messages;

@stylelint stylelint deleted a comment from sonikasharma1403 Apr 3, 2024
@stylelint stylelint deleted a comment from sonikasharma1403 Apr 3, 2024
@ybiquitous
Copy link
Member

npm run lint:scss -- --fix --skip-nx-cache

@sonikasharma1403 How does the script above pass the --fix flag to the stylelint command?

@sonikasharma1403
Copy link
Author

sonikasharma1403 commented Apr 8, 2024

It uses nx-stylelint:lint under the hood

@ybiquitous
Copy link
Member

What is nx-stylelint:lint? I'm afraid, but we can't address this issue unless you provide a reproducible minimum repository.

@Mouvedia
Copy link
Contributor

What is nx-stylelint:lint?

https://github.com/Phillip9587/nx-stylelint/

I'm afraid, but we can't address this issue unless you provide a reproducible minimum repository.

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: needs reproducible example triage needs reproducible demo or repo
Development

No branches or pull requests

3 participants