@intentius/chant-lexicon-sql 0.124.45 → 0.124.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/change-set.d.ts +1 -1
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +2 -1
- package/dist/clickhouse/apply/statements.d.ts +9 -0
- package/dist/clickhouse/apply/statements.d.ts.map +1 -1
- package/dist/clickhouse/apply/statements.js +24 -0
- package/dist/clickhouse/import/live-export.d.ts +7 -3
- package/dist/clickhouse/import/live-export.d.ts.map +1 -1
- package/dist/clickhouse/import/live-export.js +21 -5
- package/dist/clickhouse/live/named-collections.d.ts +27 -0
- package/dist/clickhouse/live/named-collections.d.ts.map +1 -0
- package/dist/clickhouse/live/named-collections.js +53 -0
- package/dist/clickhouse/live/undeclarable.d.ts +15 -3
- package/dist/clickhouse/live/undeclarable.d.ts.map +1 -1
- package/dist/clickhouse/live/undeclarable.js +18 -4
- package/dist/clickhouse/named-collections.d.ts +83 -0
- package/dist/clickhouse/named-collections.d.ts.map +1 -0
- package/dist/clickhouse/named-collections.js +203 -0
- package/dist/clickhouse/parser.d.ts.map +1 -1
- package/dist/clickhouse/parser.js +1 -1
- package/dist/clickhouse/plan/commands.d.ts +2 -1
- package/dist/clickhouse/plan/commands.d.ts.map +1 -1
- package/dist/clickhouse/plan/commands.js +17 -2
- package/dist/clickhouse/plan/diff.d.ts +15 -0
- package/dist/clickhouse/plan/diff.d.ts.map +1 -1
- package/dist/clickhouse/plan/diff.js +43 -1
- package/dist/clickhouse/plan/disruption.d.ts.map +1 -1
- package/dist/clickhouse/plan/disruption.js +5 -0
- package/dist/clickhouse/plan/rebuild-handoff.d.ts.map +1 -1
- package/dist/clickhouse/plan/rebuild-handoff.js +4 -0
- package/dist/clickhouse/plan/report.d.ts.map +1 -1
- package/dist/clickhouse/plan/report.js +6 -1
- package/dist/clickhouse/plan/rules.d.ts +5 -1
- package/dist/clickhouse/plan/rules.d.ts.map +1 -1
- package/dist/clickhouse/plan/rules.js +7 -3
- package/dist/clickhouse/plan/stream.d.ts +40 -0
- package/dist/clickhouse/plan/stream.d.ts.map +1 -0
- package/dist/clickhouse/plan/stream.js +107 -0
- package/dist/clickhouse/rebuild/observe.d.ts +3 -1
- package/dist/clickhouse/rebuild/observe.d.ts.map +1 -1
- package/dist/clickhouse/rebuild/observe.js +14 -1
- package/dist/clickhouse/review-catalog.d.ts +213 -0
- package/dist/clickhouse/review-catalog.d.ts.map +1 -0
- package/dist/clickhouse/review-catalog.js +330 -0
- package/dist/codegen/classifier-page.d.ts.map +1 -1
- package/dist/codegen/classifier-page.js +21 -4
- package/dist/codegen/clickhouse-module.d.ts.map +1 -1
- package/dist/codegen/clickhouse-module.js +3 -0
- package/dist/codegen/coverage-page.d.ts.map +1 -1
- package/dist/codegen/coverage-page.js +5 -3
- package/dist/codegen/docs.js +1 -1
- package/dist/codegen/postgres-classifier-page.js +1 -1
- package/dist/codegen/registry.d.ts.map +1 -1
- package/dist/codegen/registry.js +5 -0
- package/dist/composites/catalog.d.ts.map +1 -1
- package/dist/composites/catalog.js +100 -0
- package/dist/composites/clickhouse.d.ts +2 -0
- package/dist/composites/clickhouse.d.ts.map +1 -1
- package/dist/composites/clickhouse.js +2 -0
- package/dist/composites/otel-tables.d.ts +100 -0
- package/dist/composites/otel-tables.d.ts.map +1 -0
- package/dist/composites/otel-tables.js +282 -0
- package/dist/composites/stream-ingest.d.ts +63 -0
- package/dist/composites/stream-ingest.d.ts.map +1 -0
- package/dist/composites/stream-ingest.js +39 -0
- package/dist/coverage/clickhouse.json +1 -1
- package/dist/coverage/postgres.json +7 -7
- package/dist/coverage.d.ts +16 -6
- package/dist/coverage.d.ts.map +1 -1
- package/dist/coverage.js +10 -5
- package/dist/drizzle-command.d.ts +17 -0
- package/dist/drizzle-command.d.ts.map +1 -0
- package/dist/drizzle-command.js +91 -0
- package/dist/drizzle.d.ts +70 -0
- package/dist/drizzle.d.ts.map +1 -0
- package/dist/drizzle.js +684 -0
- package/dist/files/clickhouse.d.ts.map +1 -1
- package/dist/files/clickhouse.js +8 -1
- package/dist/files/common.d.ts +13 -0
- package/dist/files/common.d.ts.map +1 -1
- package/dist/files/common.js +5 -0
- package/dist/files/postgres.js +12 -2
- package/dist/generated/clickhouse.d.ts +2 -0
- package/dist/generated/clickhouse.d.ts.map +1 -1
- package/dist/generated/clickhouse.js +2 -0
- package/dist/import-parser.d.ts +21 -1
- package/dist/import-parser.d.ts.map +1 -1
- package/dist/import-parser.js +79 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/integrity.json +6 -5
- package/dist/lint/post-synth/audit-entries.d.ts.map +1 -1
- package/dist/lint/post-synth/audit-entries.js +1 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/index.js +2 -0
- package/dist/lint/post-synth/sqlch128.d.ts +18 -0
- package/dist/lint/post-synth/sqlch128.d.ts.map +1 -0
- package/dist/lint/post-synth/sqlch128.js +222 -0
- package/dist/lint/rules/postgres-templates.js +2 -2
- package/dist/lsp/postgres-template.d.ts.map +1 -1
- package/dist/lsp/postgres-template.js +11 -0
- package/dist/manifest.json +1 -1
- package/dist/mcp/postgres.js +1 -1
- package/dist/meta.json +25 -0
- package/dist/migrate/from-postgres/detect.d.ts +4 -0
- package/dist/migrate/from-postgres/detect.d.ts.map +1 -0
- package/dist/migrate/from-postgres/detect.js +7 -0
- package/dist/migrate/from-postgres/emit.d.ts +26 -0
- package/dist/migrate/from-postgres/emit.d.ts.map +1 -0
- package/dist/migrate/from-postgres/emit.js +86 -0
- package/dist/migrate/from-postgres/index.d.ts +51 -0
- package/dist/migrate/from-postgres/index.d.ts.map +1 -0
- package/dist/migrate/from-postgres/index.js +115 -0
- package/dist/migrate/from-postgres/translate.d.ts +73 -0
- package/dist/migrate/from-postgres/translate.d.ts.map +1 -0
- package/dist/migrate/from-postgres/translate.js +261 -0
- package/dist/migrate/from-postgres/types.d.ts +51 -0
- package/dist/migrate/from-postgres/types.d.ts.map +1 -0
- package/dist/migrate/from-postgres/types.js +211 -0
- package/dist/migration-replay.d.ts +73 -0
- package/dist/migration-replay.d.ts.map +1 -0
- package/dist/migration-replay.js +294 -0
- package/dist/okf/index.md +6 -0
- package/dist/okf/rules/SQLCH128.md +11 -0
- package/dist/okf/types/PostgresForeignDataWrapper.md +9 -0
- package/dist/okf/types/PostgresForeignServer.md +9 -0
- package/dist/okf/types/PostgresOperator.md +9 -0
- package/dist/okf/types/PostgresPublication.md +9 -0
- package/dist/okf/types/PostgresStatistics.md +9 -0
- package/dist/op/activities/postgres-migration.d.ts.map +1 -1
- package/dist/op/activities/postgres-migration.js +4 -0
- package/dist/op/activity-contracts.d.ts +20 -0
- package/dist/op/activity-contracts.d.ts.map +1 -1
- package/dist/op/activity-contracts.js +2 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +36 -2
- package/dist/postgres/apply/apply.js +8 -1
- package/dist/postgres/apply/statements.d.ts.map +1 -1
- package/dist/postgres/apply/statements.js +87 -2
- package/dist/postgres/constraint-names.d.ts +30 -0
- package/dist/postgres/constraint-names.d.ts.map +1 -0
- package/dist/postgres/constraint-names.js +72 -0
- package/dist/postgres/entities.d.ts +110 -1
- package/dist/postgres/entities.d.ts.map +1 -1
- package/dist/postgres/entities.js +146 -0
- package/dist/postgres/entity-types.d.ts +5 -0
- package/dist/postgres/entity-types.d.ts.map +1 -1
- package/dist/postgres/entity-types.js +5 -0
- package/dist/postgres/import/generator.d.ts.map +1 -1
- package/dist/postgres/import/generator.js +50 -2
- package/dist/postgres/import/live-export.d.ts.map +1 -1
- package/dist/postgres/import/live-export.js +2 -0
- package/dist/postgres/live/catalog-kinds.d.ts +14 -0
- package/dist/postgres/live/catalog-kinds.d.ts.map +1 -1
- package/dist/postgres/live/catalog-kinds.js +206 -0
- package/dist/postgres/live/catalog.d.ts +2 -0
- package/dist/postgres/live/catalog.d.ts.map +1 -1
- package/dist/postgres/live/catalog.js +3 -1
- package/dist/postgres/live/describe-resources.d.ts.map +1 -1
- package/dist/postgres/live/describe-resources.js +10 -2
- package/dist/postgres/migrate/backfill.d.ts +2 -0
- package/dist/postgres/migrate/backfill.d.ts.map +1 -1
- package/dist/postgres/migrate/backfill.js +7 -1
- package/dist/postgres/migrate/carry.d.ts +45 -1
- package/dist/postgres/migrate/carry.d.ts.map +1 -1
- package/dist/postgres/migrate/carry.js +72 -8
- package/dist/postgres/migrate/observe.d.ts +28 -4
- package/dist/postgres/migrate/observe.d.ts.map +1 -1
- package/dist/postgres/migrate/observe.js +41 -12
- package/dist/postgres/migrate/op.d.ts +26 -1
- package/dist/postgres/migrate/op.d.ts.map +1 -1
- package/dist/postgres/migrate/op.js +3 -1
- package/dist/postgres/migrate/steps.d.ts +35 -2
- package/dist/postgres/migrate/steps.d.ts.map +1 -1
- package/dist/postgres/migrate/steps.js +158 -12
- package/dist/postgres/parser.d.ts +72 -1
- package/dist/postgres/parser.d.ts.map +1 -1
- package/dist/postgres/parser.js +194 -2
- package/dist/postgres/plan/deep.d.ts.map +1 -1
- package/dist/postgres/plan/deep.js +29 -1
- package/dist/postgres/plan/diff.d.ts.map +1 -1
- package/dist/postgres/plan/diff.js +39 -1
- package/dist/postgres/plan/normalize.d.ts +17 -2
- package/dist/postgres/plan/normalize.d.ts.map +1 -1
- package/dist/postgres/plan/normalize.js +109 -1
- package/dist/postgres/plan/rules.d.ts +1 -0
- package/dist/postgres/plan/rules.d.ts.map +1 -1
- package/dist/postgres/plan/rules.js +3 -2
- package/dist/postgres/plan/schema.js +1 -1
- package/dist/postgres/secret-options.d.ts +10 -0
- package/dist/postgres/secret-options.d.ts.map +1 -0
- package/dist/postgres/secret-options.js +9 -0
- package/dist/postgres/testing/roundtrip-schema.d.ts +3 -2
- package/dist/postgres/testing/roundtrip-schema.d.ts.map +1 -1
- package/dist/postgres/testing/roundtrip-schema.js +13 -1
- package/dist/postgres.d.ts +1 -1
- package/dist/postgres.d.ts.map +1 -1
- package/dist/postgres.js +1 -1
- package/dist/rules/audit-entries.ts +1 -0
- package/dist/rules/postgres-templates.ts +2 -2
- package/dist/rules/sqlch128.ts +230 -0
- package/dist/serializer.d.ts +5 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/serializer.js +13 -2
- package/dist/sql-types-command.d.ts +13 -0
- package/dist/sql-types-command.d.ts.map +1 -1
- package/dist/sql-types-command.js +31 -16
- package/dist/sql-types.d.ts +22 -0
- package/dist/sql-types.d.ts.map +1 -1
- package/dist/sql-types.js +10 -6
- package/dist/sqlserver/lexer.d.ts +37 -0
- package/dist/sqlserver/lexer.d.ts.map +1 -0
- package/dist/sqlserver/lexer.js +254 -0
- package/dist/sqlserver/parse.d.ts +185 -0
- package/dist/sqlserver/parse.d.ts.map +1 -0
- package/dist/sqlserver/parse.js +767 -0
- package/dist/sqlserver/translate.d.ts +34 -0
- package/dist/sqlserver/translate.d.ts.map +1 -0
- package/dist/sqlserver/translate.js +495 -0
- package/dist/sqlserver/types.d.ts +44 -0
- package/dist/sqlserver/types.d.ts.map +1 -0
- package/dist/sqlserver/types.js +133 -0
- package/dist/sqlserver/validate.d.ts +31 -0
- package/dist/sqlserver/validate.d.ts.map +1 -0
- package/dist/sqlserver/validate.js +67 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +5 -0
- package/package.json +2 -2
- package/src/change-set.ts +2 -1
- package/src/clickhouse/apply/statements.ts +25 -0
- package/src/clickhouse/import/live-export.ts +20 -5
- package/src/clickhouse/live/named-collections.e2e.test.ts +88 -0
- package/src/clickhouse/live/named-collections.ts +61 -0
- package/src/clickhouse/live/undeclarable.e2e.test.ts +5 -3
- package/src/clickhouse/live/undeclarable.ts +20 -4
- package/src/clickhouse/named-collections.test.ts +187 -0
- package/src/clickhouse/named-collections.ts +224 -0
- package/src/clickhouse/parser.ts +2 -1
- package/src/clickhouse/plan/commands.ts +17 -2
- package/src/clickhouse/plan/diff.ts +57 -1
- package/src/clickhouse/plan/disruption.ts +5 -0
- package/src/clickhouse/plan/rebuild-handoff.ts +3 -0
- package/src/clickhouse/plan/report.ts +6 -1
- package/src/clickhouse/plan/rules.ts +14 -4
- package/src/clickhouse/plan/stream-s3queue.e2e.test.ts +148 -0
- package/src/clickhouse/plan/stream.test.ts +237 -0
- package/src/clickhouse/plan/stream.ts +111 -0
- package/src/clickhouse/rebuild/observe.ts +15 -1
- package/src/clickhouse/review-catalog.test.ts +181 -0
- package/src/clickhouse/review-catalog.ts +489 -0
- package/src/codegen/classifier-page.ts +21 -4
- package/src/codegen/clickhouse-module.ts +3 -0
- package/src/codegen/coverage-page.ts +5 -3
- package/src/codegen/docs.ts +1 -1
- package/src/codegen/postgres-classifier-page.ts +1 -1
- package/src/codegen/registry.ts +5 -0
- package/src/composites/catalog.test.ts +6 -1
- package/src/composites/catalog.ts +100 -0
- package/src/composites/clickhouse.ts +2 -0
- package/src/composites/composites.test.ts +1 -1
- package/src/composites/otel-tables.test.ts +118 -0
- package/src/composites/otel-tables.ts +339 -0
- package/src/composites/stream-ingest.test.ts +114 -0
- package/src/composites/stream-ingest.ts +82 -0
- package/src/core/undeclarable.test.ts +6 -5
- package/src/coverage/clickhouse.json +1 -1
- package/src/coverage/postgres.json +7 -7
- package/src/coverage.test.ts +27 -2
- package/src/coverage.ts +18 -7
- package/src/drizzle-command.ts +94 -0
- package/src/drizzle-kit.e2e.test.ts +89 -0
- package/src/drizzle-types.e2e.test.ts +67 -0
- package/src/drizzle.test.ts +358 -0
- package/src/drizzle.ts +737 -0
- package/src/files/clickhouse.ts +8 -1
- package/src/files/common.ts +14 -1
- package/src/files/postgres.ts +12 -2
- package/src/generated/clickhouse.ts +3 -0
- package/src/generated/lexicon-sql.json +25 -0
- package/src/import-parser.sqlserver.test.ts +92 -0
- package/src/import-parser.ts +79 -5
- package/src/index.ts +1 -1
- package/src/lint/post-synth/audit-entries.ts +1 -0
- package/src/lint/post-synth/index.ts +2 -0
- package/src/lint/post-synth/sqlch128.test.ts +107 -0
- package/src/lint/post-synth/sqlch128.ts +230 -0
- package/src/lint/rules/postgres-templates.ts +2 -2
- package/src/lsp/postgres-template.ts +11 -0
- package/src/mcp/postgres.ts +1 -1
- package/src/migrate/from-postgres/cli.test.ts +165 -0
- package/src/migrate/from-postgres/detect.ts +7 -0
- package/src/migrate/from-postgres/emit.ts +96 -0
- package/src/migrate/from-postgres/example.test.ts +205 -0
- package/src/migrate/from-postgres/index.ts +122 -0
- package/src/migrate/from-postgres/translate.test.ts +242 -0
- package/src/migrate/from-postgres/translate.ts +359 -0
- package/src/migrate/from-postgres/types.test.ts +116 -0
- package/src/migrate/from-postgres/types.ts +236 -0
- package/src/migrate/from-postgres/validate.e2e.test.ts +44 -0
- package/src/migrate/from-postgres/workspace.e2e.test.ts +114 -0
- package/src/migration-replay.e2e.test.ts +155 -0
- package/src/migration-replay.test.ts +173 -0
- package/src/migration-replay.ts +289 -0
- package/src/op/__snapshots__/watch-pipeline.test.ts.snap +2 -2
- package/src/op/activities/postgres-migration.ts +4 -0
- package/src/op/activity-contracts.ts +2 -0
- package/src/plugin.ts +37 -2
- package/src/postgres/apply/apply.ts +9 -1
- package/src/postgres/apply/statements.ts +74 -1
- package/src/postgres/constraint-names.ts +71 -0
- package/src/postgres/database-kinds.test.ts +384 -0
- package/src/postgres/entities.ts +266 -1
- package/src/postgres/entity-types.ts +5 -0
- package/src/postgres/import/generator.ts +42 -2
- package/src/postgres/import/live-export.ts +1 -0
- package/src/postgres/live/catalog-kinds.ts +223 -0
- package/src/postgres/live/catalog.ts +6 -1
- package/src/postgres/live/database-kinds.e2e.test.ts +164 -0
- package/src/postgres/live/describe-resources.ts +11 -2
- package/src/postgres/live/undeclarable.e2e.test.ts +13 -6
- package/src/postgres/migrate/backfill.ts +13 -1
- package/src/postgres/migrate/carry.ts +103 -8
- package/src/postgres/migrate/followups.e2e.test.ts +218 -0
- package/src/postgres/migrate/followups.test.ts +199 -0
- package/src/postgres/migrate/kinds.e2e.test.ts +5 -5
- package/src/postgres/migrate/observe.ts +53 -14
- package/src/postgres/migrate/op.ts +28 -1
- package/src/postgres/migrate/partition.e2e.test.ts +129 -0
- package/src/postgres/migrate/partition.test.ts +206 -0
- package/src/postgres/migrate/steps.ts +157 -12
- package/src/postgres/parser.ts +234 -2
- package/src/postgres/plan/deep.ts +28 -1
- package/src/postgres/plan/diff.ts +36 -1
- package/src/postgres/plan/normalize.ts +122 -1
- package/src/postgres/plan/rules.ts +10 -3
- package/src/postgres/plan/schema.ts +1 -1
- package/src/postgres/secret-options.ts +10 -0
- package/src/postgres/testing/roundtrip-schema.ts +13 -1
- package/src/postgres.ts +16 -0
- package/src/serializer.ts +13 -2
- package/src/skills/chant-sql-postgres-migration.md +3 -3
- package/src/skills/chant-sql-postgres.md +6 -1
- package/src/skills/chant-sql.md +2 -1
- package/src/sql-types-command.ts +38 -16
- package/src/sql-types.ts +11 -6
- package/src/sqlserver/fixture.test.ts +186 -0
- package/src/sqlserver/import-cli.test.ts +61 -0
- package/src/sqlserver/lexer.ts +260 -0
- package/src/sqlserver/parse.ts +865 -0
- package/src/sqlserver/translate.test.ts +396 -0
- package/src/sqlserver/translate.ts +558 -0
- package/src/sqlserver/types.test.ts +102 -0
- package/src/sqlserver/types.ts +154 -0
- package/src/sqlserver/validate.e2e.test.ts +32 -0
- package/src/sqlserver/validate.test.ts +49 -0
- package/src/sqlserver/validate.ts +76 -0
- package/src/validate.ts +5 -0
- package/src/workspace-kinds.test.ts +11 -0
- package/workspace-kinds.json +100 -2
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The first two Done-when boxes of #4398, on a public sample schema: an
|
|
3
|
+
* excerpt of Microsoft's WideWorldImporters (MIT, vendored in
|
|
4
|
+
* `test-fixtures/sqlserver/` with its licence). The schema imports, every
|
|
5
|
+
* non-mechanical decision is a SARIF diagnostic, and the output builds and
|
|
6
|
+
* passes the sql lexicon's lint rules and post-synth checks.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { afterAll, describe, expect, test } from "vitest";
|
|
12
|
+
import { build } from "@intentius/chant/build";
|
|
13
|
+
import { runLint } from "@intentius/chant/lint/engine";
|
|
14
|
+
import { runPostSynthChecks } from "@intentius/chant/lint/post-synth";
|
|
15
|
+
import { importExitCode, importSarif } from "@intentius/chant/cli/commands/import-report";
|
|
16
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
17
|
+
import { sqlPlugin } from "../plugin";
|
|
18
|
+
import { sqlTemplateGenerator } from "../import-generator";
|
|
19
|
+
import { postSynthChecks } from "../lint/post-synth";
|
|
20
|
+
import { CLICKHOUSE_DDL_FILE, sqlSerializer } from "../serializer";
|
|
21
|
+
import { splitStatements } from "../clickhouse/import/ir";
|
|
22
|
+
import { REVIEW_RULES } from "../clickhouse/review-catalog";
|
|
23
|
+
|
|
24
|
+
const fixtures = join(import.meta.dirname, "..", "..", "test-fixtures", "sqlserver");
|
|
25
|
+
const source = readFileSync(join(fixtures, "wwi-excerpt.sql"), "utf8");
|
|
26
|
+
const parse = (text: string, options: Record<string, string | boolean> = { from: "sqlserver" }) => sqlPlugin.templateParser!(options).parse(text);
|
|
27
|
+
const ir = parse(source);
|
|
28
|
+
const diagnostics = ir.diagnostics ?? [];
|
|
29
|
+
const ddlOf = (name: string) => (ir.resources.find((r) => (r.properties as { name: string }).name === name)!.properties as { ddl: string }).ddl;
|
|
30
|
+
|
|
31
|
+
describe("the vendored sample", () => {
|
|
32
|
+
test("carries its licence, source path and commit", () => {
|
|
33
|
+
const licence = readFileSync(join(fixtures, "LICENSE-sql-server-samples.txt"), "utf8");
|
|
34
|
+
expect(licence).toContain("MIT License");
|
|
35
|
+
expect(licence).toContain("Copyright (c) Microsoft Corporation");
|
|
36
|
+
expect(source).toContain("Licence: MIT. Copyright (c) Microsoft Corporation");
|
|
37
|
+
expect(source).toContain("samples/databases/wide-world-importers/wwi-ssdt/wwi-ssdt/");
|
|
38
|
+
expect(source).toMatch(/Commit:\s+[0-9a-f]{40}/);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("importing it", () => {
|
|
43
|
+
test("every table, view and schema becomes one object, databases first", () => {
|
|
44
|
+
expect(ir.resources.map((r) => `${r.type.replace("ClickHouse::", "")} ${(r.properties as { name: string }).name}`)).toEqual([
|
|
45
|
+
"Database Warehouse",
|
|
46
|
+
"Database Sales",
|
|
47
|
+
"Database WebApi",
|
|
48
|
+
"Table StockItemTransactions",
|
|
49
|
+
"Table CustomerTransactions",
|
|
50
|
+
"Table Orders",
|
|
51
|
+
"Table Colors",
|
|
52
|
+
"Table ColdRoomTemperatures",
|
|
53
|
+
"View Colors",
|
|
54
|
+
]);
|
|
55
|
+
expect(ir.warnings ?? []).toEqual([]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("the columnstore table, the clustered-index table and the primary-key tables get the sort key the catalog picks", () => {
|
|
59
|
+
expect(ddlOf("StockItemTransactions")).toContain("ORDER BY StockItemTransactionID");
|
|
60
|
+
expect(ddlOf("CustomerTransactions")).toContain("ORDER BY TransactionDate");
|
|
61
|
+
expect(ddlOf("Orders")).toContain("ORDER BY OrderID");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("the generated declarations name their dependencies", () => {
|
|
65
|
+
const [file] = sqlTemplateGenerator.generate(ir);
|
|
66
|
+
expect(file!.content).toContain("CREATE TABLE ${warehouseDb}.StockItemTransactions");
|
|
67
|
+
expect(file!.content).toContain("FROM ${warehouseColors}");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("every non-mechanical decision is a diagnostic", () => {
|
|
72
|
+
const at = (rule: string) => diagnostics.filter((d) => d.ruleId === rule);
|
|
73
|
+
const columnsOf = (ddl: string) => [...ddl.matchAll(/^ {4}(\w+) (.+?),?$/gm)].map((m) => ({ name: m[1]!, rest: m[2]! }));
|
|
74
|
+
|
|
75
|
+
test("each Nullable column in the DDL has a SQLRV003 diagnostic naming the table and column", () => {
|
|
76
|
+
const wanted: string[] = [];
|
|
77
|
+
for (const r of ir.resources) {
|
|
78
|
+
const p = r.properties as { name: string; database?: string; ddl: string };
|
|
79
|
+
if (r.type !== "ClickHouse::Table") continue;
|
|
80
|
+
for (const c of columnsOf(p.ddl)) if (c.rest.startsWith("Nullable(")) wanted.push(`${p.database ?? ""}.${p.name}.${c.name}`.replace(/^\./, ""));
|
|
81
|
+
}
|
|
82
|
+
expect(wanted.length).toBeGreaterThan(10);
|
|
83
|
+
expect(at("SQLRV003").map((d) => d.message.split(":")[0])).toEqual(expect.arrayContaining(wanted));
|
|
84
|
+
expect(at("SQLRV003")).toHaveLength(wanted.length);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("each table has exactly one sort key diagnostic and one engine diagnostic", () => {
|
|
88
|
+
expect(at("SQLRV001").map((d) => d.entity).sort()).toEqual(["Sales.CustomerTransactions", "Sales.Orders", "Warehouse.ColdRoomTemperatures", "Warehouse.Colors", "Warehouse.StockItemTransactions"]);
|
|
89
|
+
expect(at("SQLRV002")).toHaveLength(5);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("each foreign key and unique constraint in the source is a comment in the DDL and a SQLRV005 diagnostic", () => {
|
|
93
|
+
const sourceCount = [...source.matchAll(/FOREIGN KEY|\bUNIQUE NONCLUSTERED/g)].length;
|
|
94
|
+
const commentCount = ir.resources.reduce((n, r) => n + [...((r.properties as { ddl: string }).ddl.matchAll(/^-- (?:FOREIGN KEY|UNIQUE)/gm))].length, 0);
|
|
95
|
+
expect(sourceCount).toBeGreaterThan(15);
|
|
96
|
+
expect(commentCount).toBe(sourceCount);
|
|
97
|
+
expect(at("SQLRV005")).toHaveLength(sourceCount);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("the identity column, the sequence defaults, the computed column and the period columns are SQLRV006", () => {
|
|
101
|
+
const messages = at("SQLRV006").map((d) => d.message);
|
|
102
|
+
expect(messages.filter((m) => m.includes("draws from a sequence"))).toHaveLength([...source.matchAll(/NEXT VALUE FOR/g)].length);
|
|
103
|
+
expect(messages.some((m) => m.includes("Identity column ColdRoomTemperatureID"))).toBe(true);
|
|
104
|
+
expect(messages.some((m) => m.includes("Computed column IsFinalized"))).toBe(true);
|
|
105
|
+
expect(messages.filter((m) => m.includes("filled by the system"))).toHaveLength(4);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("the sysdatetime() defaults, the view and the indexes that are not carried are diagnostics", () => {
|
|
109
|
+
expect(at("SQLRV008")).toHaveLength([...source.matchAll(/DEFAULT \(sysdatetime\(\)\)/g)].length);
|
|
110
|
+
expect(at("SQLRV010")).toHaveLength(1);
|
|
111
|
+
const notCarried = at("SQLRV009").filter((d) => d.message.includes("is not carried") && d.message.includes("index"));
|
|
112
|
+
expect(notCarried).toHaveLength([...source.matchAll(/CREATE NONCLUSTERED INDEX/g)].length + 1);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("the diagnostics use only rules in the catalog, each with a line in the source", () => {
|
|
116
|
+
for (const d of diagnostics) {
|
|
117
|
+
expect(Object.keys(REVIEW_RULES)).toContain(d.ruleId);
|
|
118
|
+
expect(d.line, d.message).toBeGreaterThan(0);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("the SARIF report has one result per diagnostic with its rule, level and region", () => {
|
|
123
|
+
const sarif = JSON.parse(importSarif("wwi-excerpt.sql", diagnostics)) as {
|
|
124
|
+
version: string;
|
|
125
|
+
runs: Array<{ results: Array<{ ruleId: string; level: string; message: { text: string }; locations: Array<{ physicalLocation: { artifactLocation: { uri: string }; region: { startLine: number } } }> }> }>;
|
|
126
|
+
};
|
|
127
|
+
expect(sarif.version).toBe("2.1.0");
|
|
128
|
+
const results = sarif.runs[0]!.results;
|
|
129
|
+
expect(results).toHaveLength(diagnostics.length);
|
|
130
|
+
expect(new Set(results.map((r) => r.level))).toEqual(new Set(["warning", "note"]));
|
|
131
|
+
expect(results[0]!.locations[0]!.physicalLocation.artifactLocation.uri).toContain("wwi-excerpt.sql");
|
|
132
|
+
expect(results.find((r) => r.ruleId === "SQLRV001")!.message.text).toContain("Evidence:");
|
|
133
|
+
expect(results.every((r) => r.locations[0]!.physicalLocation.region.startLine > 0)).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe("--strict", () => {
|
|
138
|
+
test("exits non-zero while a diagnostic to review is open, and zero without --strict", () => {
|
|
139
|
+
expect(importExitCode(diagnostics, true)).toBe(1);
|
|
140
|
+
expect(importExitCode(diagnostics, false)).toBe(0);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("a schema whose only diagnostics are notes passes --strict", () => {
|
|
144
|
+
const notes = diagnostics.filter((d) => d.severity === "info");
|
|
145
|
+
expect(notes.length).toBeGreaterThan(0);
|
|
146
|
+
expect(importExitCode(notes, true)).toBe(0);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe("refusing the sample's function", () => {
|
|
151
|
+
const refused = readFileSync(join(fixtures, "wwi-refused.sql"), "utf8");
|
|
152
|
+
|
|
153
|
+
test("the import fails and names the function and its line", () => {
|
|
154
|
+
expect(() => parse(refused)).toThrow(/function Application\.DetermineCustomerAccess \(line \d+\)/);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("with skipUndeclarable it imports the rest and warns by name", () => {
|
|
158
|
+
const withTable = `${source}\nGO\n${refused}`;
|
|
159
|
+
const r = parse(withTable, { from: "sqlserver", skipUndeclarable: true });
|
|
160
|
+
expect(r.resources).toHaveLength(ir.resources.length);
|
|
161
|
+
expect(r.diagnostics!.some((d) => d.severity === "warning" && d.message.includes("function Application.DetermineCustomerAccess"))).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// The output builds and lints.
|
|
166
|
+
const dir = join(import.meta.dirname, "..", "..", `.sqlserver-import-tmp-${process.pid}`);
|
|
167
|
+
afterAll(() => rmSync(dir, { recursive: true, force: true }));
|
|
168
|
+
|
|
169
|
+
describe("the output builds and lints clean", () => {
|
|
170
|
+
test("the declarations build to one statement per object and pass the lexicon's lint and checks", async () => {
|
|
171
|
+
const [file] = sqlTemplateGenerator.generate(ir);
|
|
172
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
173
|
+
writeFileSync(join(dir, "chant.config.ts"), 'export default { lexicons: ["sql"] };\n');
|
|
174
|
+
writeFileSync(join(dir, "src", "schema.ts"), file!.content);
|
|
175
|
+
|
|
176
|
+
const result = await build(join(dir, "src"), [sqlSerializer], undefined, { fold: true, intrinsics: sqlPlugin.intrinsics!(), lexicons: ["sql"] });
|
|
177
|
+
expect(result.errors).toEqual([]);
|
|
178
|
+
const statements = splitStatements((result.outputs.get("sql") as SerializerResult).files![CLICKHOUSE_DDL_FILE]!);
|
|
179
|
+
expect(statements).toHaveLength(ir.resources.length);
|
|
180
|
+
expect(statements.find((s) => s.includes("Warehouse.StockItemTransactions"))).toContain("ENGINE = MergeTree");
|
|
181
|
+
|
|
182
|
+
expect(runPostSynthChecks(postSynthChecks, result)).toEqual([]);
|
|
183
|
+
const lint = await runLint([join(dir, "src", "schema.ts")], sqlPlugin.lintRules!(), undefined, sqlPlugin.intrinsics!());
|
|
184
|
+
expect(lint.diagnostics.filter((d) => d.severity === "error" || d.severity === "warning")).toEqual([]);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant import` end to end in-process, for the third Done-when box of #4398:
|
|
3
|
+
* `--strict` exits non-zero while a diagnostic is open, `--report` writes the
|
|
4
|
+
* SARIF, and without `--strict` the same import exits 0.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { afterAll, beforeAll, describe, expect, test, vi } from "vitest";
|
|
11
|
+
import { parseArgs } from "@intentius/chant/cli/parse-args";
|
|
12
|
+
import { runImport } from "@intentius/chant/cli/handlers/misc";
|
|
13
|
+
|
|
14
|
+
const dir = mkdtempSync(join(tmpdir(), "sqlserver-import-cli-"));
|
|
15
|
+
const input = join(dir, "schema.sql");
|
|
16
|
+
afterAll(() => rmSync(dir, { recursive: true, force: true }));
|
|
17
|
+
beforeAll(() => writeFileSync(input, "CREATE TABLE [dbo].[T] ([Id] INT NOT NULL, [Note] NVARCHAR(40) NULL, CONSTRAINT [PK_T] PRIMARY KEY CLUSTERED ([Id]));\nGO\n"));
|
|
18
|
+
|
|
19
|
+
async function run(extra: string[], name: string) {
|
|
20
|
+
const out = join(dir, name);
|
|
21
|
+
const args = parseArgs(["import", input, "--lexicon", "sql", "--parser-option", "from=sqlserver", "--output", out, ...extra]);
|
|
22
|
+
const spy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
23
|
+
const log = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
24
|
+
try {
|
|
25
|
+
const code = await runImport({ args, plugins: [], serializers: [] });
|
|
26
|
+
return { code, out, stderr: spy.mock.calls.map((c) => String(c[0])).join("\n") };
|
|
27
|
+
} finally {
|
|
28
|
+
spy.mockRestore();
|
|
29
|
+
log.mockRestore();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("chant import --parser-option from=sqlserver", () => {
|
|
34
|
+
test("exits 0 without --strict, writes the declarations and prints the review summary", async () => {
|
|
35
|
+
const r = await run([], "plain");
|
|
36
|
+
expect(r.code).toBe(0);
|
|
37
|
+
expect(existsSync(join(r.out, "schema.ts"))).toBe(true);
|
|
38
|
+
expect(r.stderr).toContain("Review: 3 to review");
|
|
39
|
+
expect(r.stderr).toContain("SQLRV001 x1");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("--strict exits 1 while a diagnostic is open", async () => {
|
|
43
|
+
expect((await run(["--strict"], "strict")).code).toBe(1);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("--report writes SARIF with the three diagnostics, with or without --strict", async () => {
|
|
47
|
+
const report = join(dir, "review.sarif");
|
|
48
|
+
const r = await run(["--report", report], "report");
|
|
49
|
+
expect(r.code).toBe(0);
|
|
50
|
+
const sarif = JSON.parse(readFileSync(report, "utf8")) as { runs: Array<{ results: Array<{ ruleId: string; locations: Array<{ physicalLocation: { artifactLocation: { uri: string } } }> }> }> };
|
|
51
|
+
expect(sarif.runs[0]!.results.map((x) => x.ruleId).sort()).toEqual(["SQLRV001", "SQLRV002", "SQLRV003"]);
|
|
52
|
+
expect(sarif.runs[0]!.results[0]!.locations[0]!.physicalLocation.artifactLocation.uri).toContain("schema.sql");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("a refused statement fails the import and names it", async () => {
|
|
56
|
+
writeFileSync(input, "CREATE TABLE T (Id int NOT NULL);\nGO\nCREATE TRIGGER trg_t ON T AFTER INSERT AS SELECT 1\n");
|
|
57
|
+
const r = await run([], "refused");
|
|
58
|
+
expect(r.code).toBe(1);
|
|
59
|
+
expect(r.stderr).toContain("trigger trg_t (line 3)");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A T-SQL tokenizer for the statements `chant import` reads from SQL Server.
|
|
3
|
+
*
|
|
4
|
+
* T-SQL differs from the lexers the other dialects use in four places: an
|
|
5
|
+
* identifier can be `[bracketed]` (a `]` inside is written `]]`) or
|
|
6
|
+
* "double quoted", a string can carry an `N` prefix, a line holding only `GO`
|
|
7
|
+
* ends a batch (it is a client command, not a statement), and statements need
|
|
8
|
+
* no terminating `;`. Comments (`--` and nestable `/* *\/`) are dropped; each
|
|
9
|
+
* token keeps its 1-based line and column in the source.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export type TokenKind = "word" | "qident" | "string" | "number" | "variable" | "punct" | "go";
|
|
13
|
+
|
|
14
|
+
export interface Token {
|
|
15
|
+
kind: TokenKind;
|
|
16
|
+
/** As written: a `qident` keeps its brackets; a string its quotes. */
|
|
17
|
+
text: string;
|
|
18
|
+
/** A `qident`'s name, a string's value, a word's text. */
|
|
19
|
+
value: string;
|
|
20
|
+
line: number;
|
|
21
|
+
column: number;
|
|
22
|
+
/** Offset of the first character. */
|
|
23
|
+
start: number;
|
|
24
|
+
/** Offset after the last. */
|
|
25
|
+
end: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class TsqlLexError extends Error {
|
|
29
|
+
constructor(
|
|
30
|
+
message: string,
|
|
31
|
+
readonly line: number,
|
|
32
|
+
readonly column: number,
|
|
33
|
+
) {
|
|
34
|
+
super(`${message} (line ${String(line)}, column ${String(column)})`);
|
|
35
|
+
this.name = "TsqlLexError";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const WORD_START = /[A-Za-z_#@\u0080-]/;
|
|
40
|
+
const WORD_PART = /[A-Za-z0-9_#$@\u0080-]/;
|
|
41
|
+
|
|
42
|
+
/** Tokenize a file. A `GO` on a line of its own (optionally with a count) becomes one `go` token. */
|
|
43
|
+
export function tokenize(text: string): Token[] {
|
|
44
|
+
const tokens: Token[] = [];
|
|
45
|
+
let i = 0;
|
|
46
|
+
let line = 1;
|
|
47
|
+
let lineStart = 0;
|
|
48
|
+
const n = text.length;
|
|
49
|
+
// A byte order mark starts files saved by SSMS and Visual Studio.
|
|
50
|
+
if (text.charCodeAt(0) === 0xfeff) {
|
|
51
|
+
i = 1;
|
|
52
|
+
lineStart = 1;
|
|
53
|
+
}
|
|
54
|
+
const push = (kind: TokenKind, start: number, end: number, value: string, startLine: number, startCol: number) =>
|
|
55
|
+
tokens.push({ kind, text: text.slice(start, end), value, line: startLine, column: startCol, start, end });
|
|
56
|
+
|
|
57
|
+
while (i < n) {
|
|
58
|
+
const c = text[i]!;
|
|
59
|
+
if (c === "\n") {
|
|
60
|
+
line++;
|
|
61
|
+
i++;
|
|
62
|
+
lineStart = i;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (/\s/.test(c)) {
|
|
66
|
+
i++;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const col = i - lineStart + 1;
|
|
70
|
+
if (c === "-" && text[i + 1] === "-") {
|
|
71
|
+
while (i < n && text[i] !== "\n") i++;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (c === "/" && text[i + 1] === "*") {
|
|
75
|
+
let depth = 1;
|
|
76
|
+
const startLine = line;
|
|
77
|
+
const startCol = col;
|
|
78
|
+
i += 2;
|
|
79
|
+
while (i < n && depth > 0) {
|
|
80
|
+
if (text[i] === "/" && text[i + 1] === "*") {
|
|
81
|
+
depth++;
|
|
82
|
+
i += 2;
|
|
83
|
+
} else if (text[i] === "*" && text[i + 1] === "/") {
|
|
84
|
+
depth--;
|
|
85
|
+
i += 2;
|
|
86
|
+
} else {
|
|
87
|
+
if (text[i] === "\n") {
|
|
88
|
+
line++;
|
|
89
|
+
lineStart = i + 1;
|
|
90
|
+
}
|
|
91
|
+
i++;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (depth > 0) throw new TsqlLexError("unterminated /* comment", startLine, startCol);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (c === "[") {
|
|
98
|
+
const start = i;
|
|
99
|
+
let value = "";
|
|
100
|
+
i++;
|
|
101
|
+
for (;;) {
|
|
102
|
+
if (i >= n) throw new TsqlLexError("unterminated [identifier]", line, col);
|
|
103
|
+
if (text[i] === "]") {
|
|
104
|
+
if (text[i + 1] === "]") {
|
|
105
|
+
value += "]";
|
|
106
|
+
i += 2;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
i++;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
if (text[i] === "\n") {
|
|
113
|
+
line++;
|
|
114
|
+
lineStart = i + 1;
|
|
115
|
+
}
|
|
116
|
+
value += text[i];
|
|
117
|
+
i++;
|
|
118
|
+
}
|
|
119
|
+
push("qident", start, i, value, line, col);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (c === '"') {
|
|
123
|
+
const start = i;
|
|
124
|
+
let value = "";
|
|
125
|
+
i++;
|
|
126
|
+
for (;;) {
|
|
127
|
+
if (i >= n) throw new TsqlLexError('unterminated "identifier"', line, col);
|
|
128
|
+
if (text[i] === '"') {
|
|
129
|
+
if (text[i + 1] === '"') {
|
|
130
|
+
value += '"';
|
|
131
|
+
i += 2;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
i++;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
value += text[i];
|
|
138
|
+
i++;
|
|
139
|
+
}
|
|
140
|
+
push("qident", start, i, value, line, col);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (c === "'" || ((c === "N" || c === "n") && text[i + 1] === "'")) {
|
|
144
|
+
const start = i;
|
|
145
|
+
const startLine = line;
|
|
146
|
+
if (c !== "'") i++;
|
|
147
|
+
i++;
|
|
148
|
+
let value = "";
|
|
149
|
+
for (;;) {
|
|
150
|
+
if (i >= n) throw new TsqlLexError("unterminated string", startLine, col);
|
|
151
|
+
if (text[i] === "'") {
|
|
152
|
+
if (text[i + 1] === "'") {
|
|
153
|
+
value += "'";
|
|
154
|
+
i += 2;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
i++;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
if (text[i] === "\n") {
|
|
161
|
+
line++;
|
|
162
|
+
lineStart = i + 1;
|
|
163
|
+
}
|
|
164
|
+
value += text[i];
|
|
165
|
+
i++;
|
|
166
|
+
}
|
|
167
|
+
push("string", start, i, value, startLine, col);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (/[0-9]/.test(c) || (c === "." && /[0-9]/.test(text[i + 1] ?? ""))) {
|
|
171
|
+
const start = i;
|
|
172
|
+
if (c === "0" && (text[i + 1] === "x" || text[i + 1] === "X")) {
|
|
173
|
+
i += 2;
|
|
174
|
+
while (i < n && /[0-9A-Fa-f]/.test(text[i]!)) i++;
|
|
175
|
+
} else {
|
|
176
|
+
while (i < n && /[0-9]/.test(text[i]!)) i++;
|
|
177
|
+
if (text[i] === "." ) {
|
|
178
|
+
i++;
|
|
179
|
+
while (i < n && /[0-9]/.test(text[i]!)) i++;
|
|
180
|
+
}
|
|
181
|
+
if ((text[i] === "e" || text[i] === "E") && /[0-9+-]/.test(text[i + 1] ?? "")) {
|
|
182
|
+
i += 2;
|
|
183
|
+
while (i < n && /[0-9]/.test(text[i]!)) i++;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
push("number", start, i, text.slice(start, i), line, col);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (c === "@" && text[i + 1] !== undefined && WORD_PART.test(text[i + 1]!)) {
|
|
190
|
+
const start = i;
|
|
191
|
+
i++;
|
|
192
|
+
while (i < n && WORD_PART.test(text[i]!)) i++;
|
|
193
|
+
push("variable", start, i, text.slice(start, i), line, col);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (WORD_START.test(c)) {
|
|
197
|
+
const start = i;
|
|
198
|
+
i++;
|
|
199
|
+
while (i < n && WORD_PART.test(text[i]!)) i++;
|
|
200
|
+
const word = text.slice(start, i);
|
|
201
|
+
push("word", start, i, word, line, col);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
// Two-character operators are one token so a `<>` is not a `<` and a `>`.
|
|
205
|
+
const two = text.slice(i, i + 2);
|
|
206
|
+
if (["<>", ">=", "<=", "!=", "+=", "-=", "*=", "/=", "||", "::"].includes(two)) {
|
|
207
|
+
push("punct", i, i + 2, two, line, col);
|
|
208
|
+
i += 2;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
push("punct", i, i + 1, c, line, col);
|
|
212
|
+
i++;
|
|
213
|
+
}
|
|
214
|
+
return markBatchSeparators(tokens, text);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** `GO` alone on its line (or `GO 5`) is a separator, not a word. */
|
|
218
|
+
function markBatchSeparators(tokens: Token[], text: string): Token[] {
|
|
219
|
+
const out: Token[] = [];
|
|
220
|
+
for (let k = 0; k < tokens.length; k++) {
|
|
221
|
+
const t = tokens[k]!;
|
|
222
|
+
if (t.kind === "word" && t.value.toUpperCase() === "GO") {
|
|
223
|
+
const prev = tokens[k - 1];
|
|
224
|
+
const next = tokens[k + 1];
|
|
225
|
+
const alone = (a: Token | undefined) => a === undefined || a.line !== t.line;
|
|
226
|
+
const count = next && next.kind === "number" && next.line === t.line ? next : undefined;
|
|
227
|
+
const after = count ? tokens[k + 2] : next;
|
|
228
|
+
// The line before `GO` ends the earlier statement, and a `GO` mid-line is a name.
|
|
229
|
+
if (alone(prev) && alone(after) && !/[A-Za-z0-9_]/.test(text.slice(t.end, t.end + 1))) {
|
|
230
|
+
out.push({ ...t, kind: "go" });
|
|
231
|
+
if (count) k++;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
out.push(t);
|
|
236
|
+
}
|
|
237
|
+
return out;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Tokens split into batches at `go` tokens; empty batches are dropped. */
|
|
241
|
+
export function batches(tokens: readonly Token[]): Token[][] {
|
|
242
|
+
const out: Token[][] = [];
|
|
243
|
+
let cur: Token[] = [];
|
|
244
|
+
for (const t of tokens) {
|
|
245
|
+
if (t.kind === "go") {
|
|
246
|
+
if (cur.length > 0) out.push(cur);
|
|
247
|
+
cur = [];
|
|
248
|
+
} else cur.push(t);
|
|
249
|
+
}
|
|
250
|
+
if (cur.length > 0) out.push(cur);
|
|
251
|
+
return out;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export const upper = (t: Token | undefined): string => (t && t.kind === "word" ? t.value.toUpperCase() : "");
|
|
255
|
+
|
|
256
|
+
/** The source text of a run of tokens, from the first token's start to the last token's end. */
|
|
257
|
+
export function sourceOf(text: string, tokens: readonly Token[]): string {
|
|
258
|
+
if (tokens.length === 0) return "";
|
|
259
|
+
return text.slice(tokens[0]!.start, tokens[tokens.length - 1]!.end);
|
|
260
|
+
}
|