From 3dad3c90d0ffe4e9029a3a401eea513574608471 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sat, 9 Jan 2021 14:16:26 +1000 Subject: [PATCH] add .sql file formatter Uses the logic from the sqltools VSCode add-on, with a workaround for the use of 'type' in some table columns. By detecting the presence of 'BUILD_WORKSPACE_DIRECTORY' we can tell if the rule is running in test mode or was run directly, avoiding the need for separate check and fix rules. It might be nice to extend this to other formatting rules in the future as well. --- docs/development.md | 1 + rslib/BUILD.bazel | 6 ++++++ ts/BUILD.bazel | 13 ++++++++++++- ts/package.json | 4 ++++ ts/sql_format.bzl | 25 ++++++++++++++++++++++++ ts/sql_format.ts | 46 +++++++++++++++++++++++++++++++++++++++++++++ ts/tsconfig.json | 2 +- ts/yarn.lock | 20 ++++++++++++++++++++ 8 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 ts/sql_format.bzl create mode 100644 ts/sql_format.ts diff --git a/docs/development.md b/docs/development.md index 6d2e70078..8bd210ef8 100644 --- a/docs/development.md +++ b/docs/development.md @@ -115,6 +115,7 @@ in the relevant package: ``` bazel run //rslib:format +bazel run //rslib:sql_format bazel run //pylib:format bazel run //qt:format bazel run //ts:format diff --git a/rslib/BUILD.bazel b/rslib/BUILD.bazel index 51d89ba7a..fdfedaa54 100644 --- a/rslib/BUILD.bazel +++ b/rslib/BUILD.bazel @@ -2,6 +2,7 @@ load("@rules_proto//proto:defs.bzl", "proto_library") load("@io_bazel_rules_rust//rust:rust.bzl", "rust_binary", "rust_library", "rust_test") load("@io_bazel_rules_rust//cargo:cargo_build_script.bzl", "cargo_build_script") load(":rustfmt.bzl", "rustfmt_fix", "rustfmt_test") +load("//ts:sql_format.bzl", "sql_format") # Build script ####################### @@ -143,6 +144,11 @@ rustfmt_fix( ]), ) +sql_format( + name = "sql_format", + srcs = glob(["**/*.sql"]), +) + # fluent.proto generation ########################### # This separate step is required to make the file available to downstream consumers. diff --git a/ts/BUILD.bazel b/ts/BUILD.bazel index e3692fb73..9ac80bb3d 100644 --- a/ts/BUILD.bazel +++ b/ts/BUILD.bazel @@ -1,7 +1,18 @@ -load("//ts:prettier.bzl", "prettier") +load("//ts:prettier.bzl", "prettier", "prettier_test") +load("//ts:sql_format.bzl", "sql_format_setup") prettier() +prettier_test( + name = "format_check", + srcs = glob([ + "*.ts", + "*.js", + ]), +) + +sql_format_setup() + # Exported files ################# diff --git a/ts/package.json b/ts/package.json index d04226ca7..b31a47bc4 100644 --- a/ts/package.json +++ b/ts/package.json @@ -12,6 +12,7 @@ "@pyoner/svelte-types": "^3.4.4-2", "@rollup/plugin-commonjs": "^15.1.0", "@rollup/plugin-node-resolve": "^9.0.0", + "@sqltools/formatter": "^1.2.2", "@tsconfig/svelte": "^1.0.10", "@types/d3-array": "^2.0.0", "@types/d3-axis": "^1.0.12", @@ -22,15 +23,18 @@ "@types/d3-shape": "^1.3.2", "@types/d3-time": "^2.0.0", "@types/d3-transition": "^1.1.6", + "@types/diff": "^5.0.0", "@types/jquery": "^3.5.0", "@types/jqueryui": "^1.12.13", "@types/lodash": "^4.14.162", "@types/long": "^4.0.1", + "@types/node": "^14.14.20", "@types/react": "^16.9.53", "@types/react-dom": "^16.9.8", "@typescript-eslint/eslint-plugin": "^2.11.0", "@typescript-eslint/parser": "^2.11.0", "cross-env": "^7.0.2", + "diff": "^5.0.0", "eslint": "^6.7.2", "license-checker-rseidelsohn": "=1.1.2", "patch-package": "^6.2.2", diff --git a/ts/sql_format.bzl b/ts/sql_format.bzl new file mode 100644 index 000000000..643206093 --- /dev/null +++ b/ts/sql_format.bzl @@ -0,0 +1,25 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_library") +load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_test") + +def sql_format_setup(): + ts_library( + name = "sql_format_lib", + srcs = ["//ts:sql_format.ts"], + deps = [ + "@npm//@sqltools/formatter", + "@npm//@types/node", + "@npm//@types/diff", + "@npm//diff", + ], + visibility = ["//visibility:public"], + ) + native.exports_files(["sql_format.ts"]) + +def sql_format(name = "sql_format", srcs = [], **kwargs): + nodejs_test( + name = name, + entry_point = "//ts:sql_format.ts", + args = [native.package_name() + "/" + f for f in srcs], + data = ["//ts:sql_format_lib", "@npm//tslib", "@npm//diff"] + srcs, + **kwargs + ) diff --git a/ts/sql_format.ts b/ts/sql_format.ts new file mode 100644 index 000000000..56722eeda --- /dev/null +++ b/ts/sql_format.ts @@ -0,0 +1,46 @@ +import sqlFormatter from "@sqltools/formatter"; +import * as Diff from "diff"; +import process from "process"; +import path from "path"; +import fs from "fs"; + +const workspace = process.env.BUILD_WORKSPACE_DIRECTORY; +const wantFix = workspace !== undefined; + +function fixFile(relpath: string, newText: string): void { + const workspacePath = path.join(workspace!, relpath); + fs.writeFileSync(workspacePath, newText); +} + +function formatText(text: string): string { + const newText: string = sqlFormatter.format(text, { + indent: " ", + reservedWordCase: "upper", + }); + // 'type' is treated as a reserved word, but Anki uses it in various sql + // tables, so we don't want it uppercased + return newText.replace(/\bTYPE\b/g, "type"); +} + +let errorFound = false; +for (const path of process.argv.slice(2)) { + const orig = fs.readFileSync(path).toString(); + const formatted = formatText(orig); + if (orig !== formatted) { + if (wantFix) { + fixFile(path, formatted); + console.log(`Fixed ${path}`); + } else { + if (!errorFound) { + errorFound = true; + console.log("SQL formatting issues found:"); + } + console.log(Diff.createPatch(path, orig, formatted)); + } + } +} +if (errorFound) { + console.log("Use 'bazel run //rslib:sql_format' to fix."); + console.log(process.env.BUILD_WORKSPACE_DIRECTORY); + process.exit(1); +} diff --git a/ts/tsconfig.json b/ts/tsconfig.json index c53d47021..d25f28372 100644 --- a/ts/tsconfig.json +++ b/ts/tsconfig.json @@ -24,7 +24,7 @@ "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */, "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, "jsx": "react", - "types": ["svelte", "long"], + "types": ["svelte", "long", "node"], "noEmitHelpers": true, "importHelpers": true } diff --git a/ts/yarn.lock b/ts/yarn.lock index 89b22551e..68f194725 100644 --- a/ts/yarn.lock +++ b/ts/yarn.lock @@ -145,6 +145,11 @@ estree-walker "^1.0.1" picomatch "^2.2.2" +"@sqltools/formatter@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.2.tgz#9390a8127c0dcba61ebd7fdcc748655e191bdd68" + integrity sha512-/5O7Fq6Vnv8L6ucmPjaWbVG1XkP4FO+w5glqfkIsq3Xw4oyNAdJddbnYodNDAfjVUvo/rrSCTom4kAND7T1o5Q== + "@tsconfig/svelte@^1.0.10": version "1.0.10" resolved "https://registry.yarnpkg.com/@tsconfig/svelte/-/svelte-1.0.10.tgz#30ec7feeee0bdf38b12a50f0686f8a2e7b6b9dc0" @@ -220,6 +225,11 @@ dependencies: "@types/d3-selection" "^1" +"@types/diff@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/diff/-/diff-5.0.0.tgz#eb71e94feae62548282c4889308a3dfb57e36020" + integrity sha512-jrm2K65CokCCX4NmowtA+MfXyuprZC13jbRuwprs6/04z/EcFg/MCwYdsHn+zgV4CQBiATiI7AEq7y1sZCtWKA== + "@types/eslint-visitor-keys@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" @@ -279,6 +289,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.38.tgz#66a7c068305dbd64cf167d0f6b6b6be71dd453e1" integrity sha512-oxo8j9doh7ab9NwDA9bCeFfjHRF/uzk+fTljCy8lMjZ3YzZGAXNDKhTE3Byso/oy32UTUQIXB3HCVHu3d2T3xg== +"@types/node@^14.14.20": + version "14.14.20" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.20.tgz#f7974863edd21d1f8a494a73e8e2b3658615c340" + integrity sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A== + "@types/prop-types@*": version "15.7.3" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" @@ -959,6 +974,11 @@ dezalgo@^1.0.0: asap "^2.0.0" wrappy "1" +diff@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"