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"