From 9d30e8e8dc273b64b562ed5f3c66b191c59e5a79 Mon Sep 17 00:00:00 2001 From: Tim Burks Date: Fri, 10 Mar 2023 15:40:22 -0800 Subject: [PATCH] Add JavaScript example of a linter plugin. --- lint/examples/javascript/.eslintignore | 2 + lint/examples/javascript/.eslintrc.yml | 14 +++ lint/examples/javascript/.gitignore | 3 + lint/examples/javascript/.prettierignore | 2 + lint/examples/javascript/.prettierrc | 8 ++ lint/examples/javascript/README.md | 113 ++++++++++++++++++ lint/examples/javascript/package.json | 26 ++++ lint/examples/javascript/registry-lint-js | 3 + .../javascript/sample-styleguide.yaml | 41 +++++++ lint/examples/javascript/src/index.js | 77 ++++++++++++ 10 files changed, 289 insertions(+) create mode 100644 lint/examples/javascript/.eslintignore create mode 100644 lint/examples/javascript/.eslintrc.yml create mode 100644 lint/examples/javascript/.gitignore create mode 100644 lint/examples/javascript/.prettierignore create mode 100644 lint/examples/javascript/.prettierrc create mode 100644 lint/examples/javascript/README.md create mode 100644 lint/examples/javascript/package.json create mode 100755 lint/examples/javascript/registry-lint-js create mode 100644 lint/examples/javascript/sample-styleguide.yaml create mode 100644 lint/examples/javascript/src/index.js diff --git a/lint/examples/javascript/.eslintignore b/lint/examples/javascript/.eslintignore new file mode 100644 index 00000000..855ad8a6 --- /dev/null +++ b/lint/examples/javascript/.eslintignore @@ -0,0 +1,2 @@ +node_modules/ +pbjs-genfiles/ diff --git a/lint/examples/javascript/.eslintrc.yml b/lint/examples/javascript/.eslintrc.yml new file mode 100644 index 00000000..53f3165e --- /dev/null +++ b/lint/examples/javascript/.eslintrc.yml @@ -0,0 +1,14 @@ +--- +extends: + - 'eslint:recommended' + - 'plugin:node/recommended' + - prettier +plugins: + - node + - prettier +rules: + prettier/prettier: error + block-scoped-var: error + eqeqeq: error + no-warning-comments: warn + no-console: off diff --git a/lint/examples/javascript/.gitignore b/lint/examples/javascript/.gitignore new file mode 100644 index 00000000..b9fc7177 --- /dev/null +++ b/lint/examples/javascript/.gitignore @@ -0,0 +1,3 @@ +node_modules +pbjs-genfiles/* +package-lock.json diff --git a/lint/examples/javascript/.prettierignore b/lint/examples/javascript/.prettierignore new file mode 100644 index 00000000..855ad8a6 --- /dev/null +++ b/lint/examples/javascript/.prettierignore @@ -0,0 +1,2 @@ +node_modules/ +pbjs-genfiles/ diff --git a/lint/examples/javascript/.prettierrc b/lint/examples/javascript/.prettierrc new file mode 100644 index 00000000..df6eac07 --- /dev/null +++ b/lint/examples/javascript/.prettierrc @@ -0,0 +1,8 @@ +--- +bracketSpacing: false +printWidth: 80 +semi: true +singleQuote: true +tabWidth: 2 +trailingComma: es5 +useTabs: false diff --git a/lint/examples/javascript/README.md b/lint/examples/javascript/README.md new file mode 100644 index 00000000..cd764a10 --- /dev/null +++ b/lint/examples/javascript/README.md @@ -0,0 +1,113 @@ +# JavaScript Linter Plugin Sample + +This directory contains a sample JavaScript program that works +as a plugin for the registry tool. + +** WARNING: this is probably fragile and poorly-written JavaScript - PRs and issues are welcome! ** + +## Protocol Buffer Support + +Message serialization requires JavaScript support code which is generated by +`pbjs` tool (part of [protobuf.js](https://www.npmjs.com/package/protobufjs)). +The generation will be done automatically after `npm install` and the resulting +`.js` file will be placed into `pbjs-genfiles/` folder. + +## Instructions + +To run the sample, first clone the `registry` project into your home directory. +We'll need this to get protos used by the plugin. + +Then clone this repository and `cd` to this directory. + +Build the code: +``` +npm install +``` + +Install the styleguide into your registry: +``` +registry apply -f sample-styleguide.yaml +``` + +To run the linter, first edit `registry-lint-js` to use the full absolute +path to `src/index.js`. + +Copy `registry-lint-js` somewhere on your search path ("." won't work, +when the linter is run, it runs in a temporary directory containing the +downloaded spec). + +Compute conformance with `registry compute conformance`. For example: +``` +registry compute conformance apis/wordnik.com/versions/4.0/specs/openapi +``` + +Be sure that your target spec's MIME type is included in the style guide's +`mimetypes` list. + +View the results of your run with `registry get`. +``` +registry get apis/wordnik.com/versions/4.0/specs/openapi/artifacts/conformance-sample-styleguide -o yaml +``` + +Here's what I get for that: +``` +$ registry get apis/wordnik.com/versions/4.0/specs/openapi/artifacts/conformance-sample-styleguide -o yaml +apiVersion: apigeeregistry/v1 +kind: ConformanceReport +metadata: + name: conformance-sample-styleguide + parent: apis/wordnik.com/versions/4.0/specs/openapi +data: + styleguide: projects/menagerie/locations/global/artifacts/sample-styleguide + guidelineReportGroups: + - state: STATE_UNSPECIFIED + guidelineReports: [] + - state: PROPOSED + guidelineReports: [] + - state: ACTIVE + guidelineReports: + - guidelineId: active + ruleReportGroups: + - severity: SEVERITY_UNSPECIFIED + ruleReports: [] + - severity: ERROR + ruleReports: + - ruleId: description-less-than-1000-chars + spec: projects/menagerie/locations/global/apis/wordnik.com/versions/4.0/specs/openapi@e70a3cc7 + file: swagger.yaml + suggestion: keep API-ing! + location: + startPosition: + lineNumber: 2 + columnNumber: 3 + endPosition: + lineNumber: 4 + columnNumber: 5 + displayName: Description Maximum Length + description: Descriptions should not be too long. + docUri: https://github.com/apigee/registry + - ruleId: description-contains-no-tags + spec: projects/menagerie/locations/global/apis/wordnik.com/versions/4.0/specs/openapi@e70a3cc7 + file: swagger.yaml + suggestion: keep API-ing! + location: + startPosition: + lineNumber: 2 + columnNumber: 3 + endPosition: + lineNumber: 4 + columnNumber: 5 + displayName: Description Tags + description: Descriptions should not contain HTML tags. + docUri: https://github.com/apigee/registry + - severity: WARNING + ruleReports: [] + - severity: INFO + ruleReports: [] + - severity: HINT + ruleReports: [] + - state: DEPRECATED + guidelineReports: [] + - state: DISABLED + guidelineReports: [] +``` diff --git a/lint/examples/javascript/package.json b/lint/examples/javascript/package.json new file mode 100644 index 00000000..3fec9c01 --- /dev/null +++ b/lint/examples/javascript/package.json @@ -0,0 +1,26 @@ +{ + "name": "registry-lint-js", + "version": "0.0.1", + "description": "Example Linter plugin written in JavaScript", + "main": "src/index.js", + "scripts": { + "lint": "eslint src/*.js", + "compile-proto": "mkdir -p pbjs-genfiles && pbjs -p ~/registry google/cloud/apigeeregistry/v1/style/lint.proto -o ./pbjs-genfiles/proto.js -t static-module", + "postinstall": "npm run compile-proto", + "prettier": "prettier --write **/*.js", + "start": "node src/index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Google LLC", + "license": "Apache-2.0", + "dependencies": { + "protobufjs": "^6.8.8" + }, + "devDependencies": { + "eslint": "^5.16.0", + "eslint-config-prettier": "^4.3.0", + "eslint-plugin-node": "^9.1.0", + "eslint-plugin-prettier": "^3.1.0", + "prettier": "^1.17.1" + } +} diff --git a/lint/examples/javascript/registry-lint-js b/lint/examples/javascript/registry-lint-js new file mode 100755 index 00000000..658e71ca --- /dev/null +++ b/lint/examples/javascript/registry-lint-js @@ -0,0 +1,3 @@ +#!/bin/sh + +node /home/FIXME/registry-experimental/lint/examples/javascript/src/index.js diff --git a/lint/examples/javascript/sample-styleguide.yaml b/lint/examples/javascript/sample-styleguide.yaml new file mode 100644 index 00000000..bfc923ac --- /dev/null +++ b/lint/examples/javascript/sample-styleguide.yaml @@ -0,0 +1,41 @@ +apiVersion: apigeeregistry/v1 +kind: StyleGuide +metadata: + name: sample-styleguide +data: + displayName: sample styleguide + mimeTypes: + - application/x.openapi+gzip;version=3.0.0 + - application/x.openapi+gzip;version=3.0.1 + - application/x.openapi+gzip;version=3.0.2 + - application/x.openapi+gzip;version=3.0.3 + - application/x.openapi+gzip;version=3.1.0 + - application/x.openapi;version=3 + - application/x.openapi+gzip;version=2.0 + - application/x.openapi+gzip;version=2 + - application/x.openapi;version=2 + - application/x.openapi+gzip + - application/x.openapi + guidelines: + - id: active + displayName: Active checks + description: Checks that are ACTIVE. + rules: + - id: description-less-than-1000-chars + displayName: Description Maximum Length + description: Descriptions should not be too long. + linter: js + linterRulename: description-less-than-1000-chars + severity: ERROR + docUri: https://github.com/apigee/registry + - id: description-contains-no-tags + displayName: Description Tags + description: Descriptions should not contain HTML tags. + linter: js + linterRulename: description-contains-no-tags + severity: ERROR + docUri: https://github.com/apigee/registry + state: ACTIVE + linters: + - name: js + uri: https://github.com/apigee/registry diff --git a/lint/examples/javascript/src/index.js b/lint/examples/javascript/src/index.js new file mode 100644 index 00000000..fef639d7 --- /dev/null +++ b/lint/examples/javascript/src/index.js @@ -0,0 +1,77 @@ +// This loads definitions of the structs in our protocol buffer models. +const Style = require('../pbjs-genfiles/proto').google.cloud.apigeeregistry.v1.style; + +async function main() { + + // The plugin expects a request message on stdin. + process.stdin.on("data", data => { + + // The request should be an encoded LinterRequest. + // Decode it. + const request = Style.LinterRequest.decode( + data + ); + + // Log it. + process.stderr.write(JSON.stringify(request) + "\n"); + + // We're going to make up some fake results. + // Here we one sample violation of each rule that was listed in the request. + problems = request.ruleIds.map(x => Style.LintProblem.fromObject({ + "ruleId": x, + "message": "it is violated", + "suggestion": "keep API-ing!", + "location": { + "startPosition": { + "lineNumber": 2, + "columnNumber": 3 + }, + "endPosition": { + "lineNumber": 4, + "columnNumber": 5, + } + } + })) + + // The request included a directory that contains the spec to lint, + // but we would have to look there to get its file name. + // Assume for now that we are linting a file named "swagger.yaml". + file = Style.LintFile.fromObject({ + "filePath": request.specDirectory + "/swagger.yaml", + "problems": problems + }); + + // Put this all together in a response message. + response = Style.LinterResponse.fromObject({ + "lint": { + "name": "registry-lint.js", + "files": [file] + } + }); + + // Log the response message. + process.stderr.write(JSON.stringify(response) + "\n"); + + // Encode the response and write it to stdout. + const responseBuffer = Style.LinterResponse.encode( + response + ).finish(); + process.stdout.write(responseBuffer) + + // We're done. + process.exit(); + }) + + // We just need this to wait until the handler above finishes. + await sleep(1000); +} + +main().catch(err => { + console.error(err); +}); + +function sleep(ms) { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +}