chris48s / v8r

✔️ A command-line JSON, YAML and TOML validator that's on your wavelength
https://chris48s.github.io/v8r/
MIT License
28 stars 5 forks source link

plugin interface: internals #477

Closed chris48s closed 1 month ago

chris48s commented 1 month ago

Initial go at a plugin interface. More work to do here..

Next steps:

I reckon you can probably merge a lot of this as just changes to internals before you expose/document it as an end user facing feature.

chris48s commented 1 month ago

~Thought:~

~If I use this hook to add a new document format that we can parse, there is no way to register it as a valid format in config-schema.json because it is just a static file.~

~Similarly, if you imagine I implement a hook that allows me to define another output format (beyond text and json).~

~I think I either need to~

~Maybe in addition to defining a document parser and/or output formatter, plugins also need to declare strings they want to add to the enum(s). In the case of the output formatter, I also need to be able to inject another option into the yargs format arg~

~I reckon implementing the existing text and json formatters as internal plugins will help define the shape of this.~

chris48s commented 1 month ago

~think about naming convention for plugins (think ESLint)~

chris48s commented 1 month ago

Plugin mood room (for architectures, docs, etc): ESLint, Webpack

chris48s commented 1 month ago

I can see other plugin hooks that might be useful to expose. However, I think this is the palce to stop pulling the thread. The first iteration of the plugin interface should expose only

Then I can iterate from there.

chris48s commented 1 month ago

As a test, here's a version of the unfinished SARIF work from https://github.com/chris48s/v8r/pull/217 implemented as a plugin using the registerOutputFormats() and logAllResults() hooks. This is written in such a way it could live outside this codebase. The key thing here is resolving our way to v8r 's package.json using require.resolve("v8r"); instead of a relative path:

import { createRequire } from "module";
// TODO: once JSON modules is stable these requires could become imports
// https://nodejs.org/api/esm.html#esm_experimental_json_modules
const require = createRequire(import.meta.url);

import path from "path";
import url from "url";

import {
  SarifBuilder,
  SarifRunBuilder,
  SarifResultBuilder,
} from "node-sarif-builder";

import { BasePlugin } from "v8r";

class SarifOutput extends BasePlugin {
  static name = "v8r-plugin-sarif-output";

  registerOutputFormats() {
    return ["sarif"];
  }

  getAllResultsLogMessage(results, format) {
    if (format === "sarif") {
      const v8rMain = require.resolve("v8r");
      const pkg = require(
        path.join(path.parse(v8rMain).dir, "..", "package.json"),
      );

      const sarifBuilder = new SarifBuilder();
      const sarifRunBuilder = new SarifRunBuilder().initSimple({
        toolDriverName: pkg.name,
        toolDriverVersion: pkg.version,
        url: pkg.homepage,
      });
      for (const result of Object.values(results)) {
        if (result.valid) {
          const sarifResultBuilder = new SarifResultBuilder();
          const sarifResultInit = {
            level: "note",
            messageText: "file is valid",
            fileUri: url.pathToFileURL(result.fileLocation).href,
          };
          sarifResultBuilder.initSimple(sarifResultInit);
          sarifRunBuilder.addResult(sarifResultBuilder);
          continue;
        }

        for (const error of result.errors) {
          const sarifResultBuilder = new SarifResultBuilder();
          const sarifResultInit = {
            level: "error",
            ruleId: error.keyword,
            messageText: error.message,
            fileUri: url.pathToFileURL(result.fileLocation).href,
          };
          sarifResultBuilder.initSimple(sarifResultInit);
          sarifRunBuilder.addResult(sarifResultBuilder);
        }
      }
      sarifBuilder.addRun(sarifRunBuilder);
      const sarifJsonString = sarifBuilder.buildSarifJsonString({
        indent: true,
      });
      return sarifJsonString;
    }
  }
}

export default SarifOutput;
chris48s commented 1 month ago

~I think the next key thing to do is to allow loading userland plugins. This requires doing the big refactor of the config loader/bootstrap process.~

Then I think once you've done that its mostly:

chris48s commented 1 month ago

Question: What would be needed to get to a stage where you can merge this as an internals refactor without actually documenting the plugin interface yet? Is that a sensible next goal to aim for?

chris48s commented 1 month ago

OK, so I reckon to get this to a stage where you can merge this the things you need to sort out are:

If you can do that, I reckon it makes sense to merge this PR just as an internal refactoring of how the existing document parser and output formats are implemented.

Then once you've done that, the follow-up tasks to launch it as a feature people can use are:

..and then tasks that are not necessary for minimum viable plugins but punt to issues for later are:

chris48s commented 1 month ago

OK. I reckon I'm going to leave the schema where it is.

I think everything else is done except

codecov[bot] commented 1 month ago

Codecov Report

Attention: Patch coverage is 97.08738% with 12 lines in your changes missing coverage. Please review.

Project coverage is 95.96%. Comparing base (761f595) to head (1cd9bc0).

Files Patch % Lines
src/plugins.js 94.25% 5 Missing :warning:
src/plugins/parser-json.js 92.00% 2 Missing :warning:
src/plugins/parser-toml.js 90.90% 2 Missing :warning:
src/plugins/parser-yaml.js 90.90% 2 Missing :warning:
src/parser.js 91.66% 1 Missing :warning:
Additional details and impacted files ```diff @@ Coverage Diff @@ ## main #477 +/- ## ========================================== + Coverage 95.39% 95.96% +0.56% ========================================== Files 11 20 +9 Lines 803 1139 +336 Branches 180 260 +80 ========================================== + Hits 766 1093 +327 - Misses 36 45 +9 Partials 1 1 ```

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.

chris48s commented 1 month ago

When you write up all the follow-up issues, add them to this milestone: https://github.com/chris48s/v8r/milestone/1