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

Integrate OnkarRuikar/markdownlint-rule-search-replace #673

Open
nschonni opened this issue Dec 8, 2022 · 13 comments
Open

Integrate OnkarRuikar/markdownlint-rule-search-replace #673

nschonni opened this issue Dec 8, 2022 · 13 comments

Comments

@nschonni
Copy link
Contributor

nschonni commented Dec 8, 2022

As part of the Markdownlint cleanup on mdn/content, @OnkarRuikar made this interesting rule https://github.com/OnkarRuikar/markdownlint-rule-search-replace

This is a very useful swiss army knife rule that allows projects to identify (and optionally fix) issues specific to their code base.

One of the limitations that might be able to be addressed if move here, is that the entire ruleset needs to be disabled if you want to disable a single rule in a file. I believe that the limitation currently is that the disable syntax doesn't support extra metadata like mardownlint-disable search-replace:my-sub-rule1

Maybe if integrating it doesn't make sense, then I could spin up a separate issue around that metadata disable part

@DavidAnson
Copy link
Owner

That's really neat, I love it!! It's not a "normal" rule like the existing MD### rules, so my current thinking is to leave it separate. (I see it's already tagged so it will be easier to find.)

/cc @OnkarRuikar

@nschonni
Copy link
Contributor Author

nschonni commented Dec 8, 2022

No problem, maybe they can explain the issue with disabling a nested custom rule better and we can repurpose this issue.

@OnkarRuikar
Copy link
Contributor

I love it!! It's not a "normal" rule like the existing MD### rules, so my current thinking is to leave it separate.

If we look at this from other perspective, this feature eases the markdownlint custom rule creation; non-developers can define their rules easily by simply editing the config file. So this is not a custom rule but a custom rule creation feature. Implementing the feature directly in markdownlint will ensure those custom rules provide same functionality as normal MD### rules do.
With this, markdownlint can become a framework for implementing and enforcing such rules in documentation projects. Every org/project has it's documentation dos and don'ts, which can be enforced with this feature.

If we want to keep it separate then can we mention it in markdownlint/README.md file? Because people may find coding custom rules difficult for various reasons.


One of the limitations that might be able to be addressed if move here, is that the entire ruleset needs to be disabled if you want to disable a single rule in a file. I believe that the limitation currently is that the disable syntax doesn't support extra metadata like mardownlint-disable search-replace:my-sub-rule1

For context behind this refer mdn/content#21432 (comment).

Following issues need to be addressed to support inline enable/disable comments(lets call it inline configurations):

  1. Inline configurations are not exposed to the custom rules. The params object in function rule(params, onError) doesn't have this info.
  2. All HTML comments are stripped before passing the content to custom rules.
    The custom rule sees <!--...............--> and not the actual inline configuration, so it can't implement the logic on it's own.

Re-implementing the entire feature in custom rules is not feasible in long term, we'll have to keep synchronizing the feature with the markdownlint repo. So we shouldn't address the issue no. 2 mentioned above.
On the other side, Markdownlint won't know about the sub-rules so it can't enforce the inline configurations itself. Only way I see is that markdownlint provides the configurations and helper methods to custom rules.

I think following provisions from markdownlint repo are required:
a. Decide on syntax for the markup which will work with existing stuff e.g.
<!-- markdownlint-disable search-replace(fqdn-moz-links, m-dash, curly-double-quotes) MD005 -->
This shouldn't disable the entire rule, but used for passing the info to the custom rule.
<!-- markdownlint-disable search-replace --> should continue disabling the entire rule.

b. Pass the configuration object to custom rules. More simplifications to the object are welcomed:

  • passing only the rule related info
  • breaking search-replace(fqdn-moz-links, m-dash) to search-replace:fqdn-moz-links and search-replace:m-dash configs

c. The helpers.js need to provide a function to convert the config object(b.) to ranges object like existing codeBlockAndSpanRanges function does.

@DavidAnson
Copy link
Owner

I am happy to include a link to this rule from the documentation! If you'd like to send a PR, I expect to publish a new release soon, and it could be part of that.

Regarding your suggestions around passing more context to rules when disabling them, you make good points. I don't want to rush into that, so I'm not going to make it part of this release, but I have added a note to myself to think about it for the following release.

Thanks again for the contribution, I really like what you've done to make custom rules more approachable!

@nschonni
Copy link
Contributor Author

nschonni commented Dec 8, 2022

BTW, I realized afterwards, that this could also replace something like MD044 https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md044---proper-names-should-have-the-correct-capitalization

@DavidAnson
Copy link
Owner

@OnkarRuikar

The “selectively disable part of a rule” proposal feels weird and special-case to me. I think a more general approach might be to allow reconfiguring a rule inline, but that would be a significant change. Since yours is the only scenario I recall where this has come up so far, what about an alternative approach? You could re-export the markdownlint-rule-search-replace rule multiple times under different names and then configure them with the different sets of strings and disable the set you want by rule name in the usual manner. If it works, this should be possible today. To be clearer, I’m imagining something like this (from memory on my phone):

const rule = require(“markdownlint-rule-search-replace”);
module.exports = [
  {
    ...rule,
    “names”: [ “rule-one” ]
  },
  {
    ...rule,
    “names”: [ “rule-two” ]
  }
];

@OnkarRuikar
Copy link
Contributor

The “selectively disable part of a rule” proposal feels weird and special-case to me.

In short, the custom rule only needs disabled line ranges and names of the disabled sub-rules.

what about an alternative approach? You could re-export the markdownlint-rule-search-replace rule multiple times under different names and then configure them with the different sets of strings and disable the set you want by rule name in the usual manner.

Are you suggesting to write a new custom rule in the target repo(say mdn/content) that will do the splitting or to write a wrapper around markdownlint-cli2 tool?

Is it possible to export multiple rule definitions from a single custom rule file, i.e. module.exports = [r1, r2, ...]? Do we have to split the configuration as well?

@DavidAnson
Copy link
Owner

I am proposing a new "meta-rule" which exports an array of multiple instances of the existing rule like you show (already supported today). Each name would need to be unique, but the implementation should all be the same, so the pattern I show should be all the extra "code" that's needed. If you specify configuration via JSON, it would need to be duplicated for each unique name. If you specify configuration via JS, you could reuse a common base object for each instance. You shouldn't have to do anything to the CLI and this should work with all the tools, I think.

@DavidAnson
Copy link
Owner

Regarding splitting configuration, it looks like you have about eight different definitions in the file you link to. In the extreme, you could give each one its own instance of the sub-rule, therefore exporting eight copies from the meta-rule. Or you could just break off one or two of those that need to be disabled inline so your meta-rule would only have two or three exports.

@DavidAnson
Copy link
Owner

Actually, the name "meta-rule" is a bad choice on my part. A single file can export multiple rules in an array. That's all I'm suggesting. Each one of those would have a new name but would otherwise be exactly the definition your rule has today.

@OnkarRuikar
Copy link
Contributor

OnkarRuikar commented Jul 14, 2023

In the extreme, you could give each one its own instance of the sub-rule, therefore exporting eight copies from the meta-rule.

I think we don't have to split the sub-rules in the config file. We can simply, in the "meta-rule", intercept the sub-rule's function call and update params object to have only the required sub-rule configuration.

// search-replace-spllitter.js (aka meta-rule)

const rule = require(“markdownlint-rule-search-replace”);
const config = readConfig(".markdownlint-cli2.jsonc");

const subRules = [];

for (const subRuleConfig in config["search-replace"].rules) {
  const newRule = { 
    ...rule, 
    "names": [subRuleConfig.name]
  };
  const originalFunction = rule.function.bind(newRule);

  newRule.function = (params, onError) => {   // wrap the "function" definition 
    // and tweak the "params" to have only the required rule config
    params.config.rules = [subRuleConfig];

    return originalFunction(params, onError);
  };

  subRules.add(newRule);
}

module.exports = subRules;

This way the config file will remain the same, non weird, for the normal contributors.

Edit: updated the function binding location.

@DavidAnson
Copy link
Owner

Smart!!! (Just curious, why do you need to bind vs. calling rule.function with the modified params directly?

@OnkarRuikar
Copy link
Contributor

Smart!!! (Just curious, why do you need to bind vs. calling rule.function with the modified params directly?

In case the custom rule uses this in future and to not to rely on original rule object to get the function reference.
I did the binding at the wrong place though. I've updated the code in the comment.

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

No branches or pull requests

3 participants