From 70527781d0f2ed9511afef98846e642cdb6fd9ae Mon Sep 17 00:00:00 2001 From: Daniel Castro Date: Thu, 1 Dec 2022 18:52:45 +0000 Subject: [PATCH] comment per statement to disable static validation --- .../function/static_error_disabled.pgsql.json | 403 ------------------ .../static_error_disabled.pgsql | 5 +- .../trigger/static_error_disabled.pgsql.json | 184 ++++++++ ..._error_trigger_column_does_not_exist.pgsql | 0 ...r_trigger_column_does_not_exist.pgsql.json | 2 +- .../queries/queryFileStaticAnalysis.ts | 65 +-- server/src/services/validation.test.ts | 18 +- server/src/utilities/regex.ts | 4 +- 8 files changed, 240 insertions(+), 441 deletions(-) delete mode 100644 sample/definitions/function/static_error_disabled.pgsql.json rename sample/definitions/{function => trigger}/static_error_disabled.pgsql (75%) create mode 100644 sample/definitions/trigger/static_error_disabled.pgsql.json rename sample/definitions/{function => trigger}/static_error_trigger_column_does_not_exist.pgsql (100%) rename sample/definitions/{function => trigger}/static_error_trigger_column_does_not_exist.pgsql.json (99%) diff --git a/sample/definitions/function/static_error_disabled.pgsql.json b/sample/definitions/function/static_error_disabled.pgsql.json deleted file mode 100644 index 2adb046..0000000 --- a/sample/definitions/function/static_error_disabled.pgsql.json +++ /dev/null @@ -1,403 +0,0 @@ -[ - { - "RawStmt": { - "stmt": { - "DropStmt": { - "objects": [ - { - "List": { - "items": [ - { - "String": { - "str": "users_1" - } - } - ] - } - } - ], - "removeType": "OBJECT_TABLE", - "behavior": "DROP_CASCADE", - "missing_ok": true - } - }, - "stmt_len": 36 - } - }, - { - "RawStmt": { - "stmt": { - "DropStmt": { - "objects": [ - { - "List": { - "items": [ - { - "String": { - "str": "users_2" - } - } - ] - } - } - ], - "removeType": "OBJECT_TABLE", - "behavior": "DROP_CASCADE", - "missing_ok": true - } - }, - "stmt_len": 37 - } - }, - { - "RawStmt": { - "stmt": { - "DropStmt": { - "objects": [ - { - "List": { - "items": [ - { - "String": { - "str": "users_3" - } - } - ] - } - } - ], - "removeType": "OBJECT_TABLE", - "behavior": "DROP_CASCADE", - "missing_ok": true - } - }, - "stmt_len": 37 - } - }, - { - "RawStmt": { - "stmt": { - "CreateStmt": { - "relation": { - "relname": "users_1", - "inh": true, - "relpersistence": "p" - }, - "tableElts": [ - { - "ColumnDef": { - "colname": "id", - "typeName": { - "names": [ - { - "String": { - "str": "pg_catalog" - } - }, - { - "String": { - "str": "int4" - } - } - ], - "typemod": -1 - }, - "is_local": true, - "constraints": [ - { - "Constraint": { - "contype": "CONSTR_NOTNULL" - } - }, - { - "Constraint": { - "contype": "CONSTR_PRIMARY" - } - } - ] - } - }, - { - "ColumnDef": { - "colname": "updated_at", - "typeName": { - "names": [ - { - "String": { - "str": "pg_catalog" - } - }, - { - "String": { - "str": "timestamptz" - } - } - ], - "typemod": -1 - }, - "is_local": true, - "constraints": [ - { - "Constraint": { - "contype": "CONSTR_NOTNULL" - } - }, - { - "Constraint": { - "contype": "CONSTR_DEFAULT", - "raw_expr": { - "FuncCall": { - "funcname": [ - { - "String": { - "str": "now" - } - } - ] - } - } - } - } - ] - } - } - ], - "oncommit": "ONCOMMIT_NOOP" - } - }, - "stmt_len": 122 - } - }, - { - "RawStmt": { - "stmt": { - "CreateStmt": { - "relation": { - "relname": "users_2", - "inh": true, - "relpersistence": "p" - }, - "tableElts": [ - { - "ColumnDef": { - "colname": "id", - "typeName": { - "names": [ - { - "String": { - "str": "pg_catalog" - } - }, - { - "String": { - "str": "int4" - } - } - ], - "typemod": -1 - }, - "is_local": true, - "constraints": [ - { - "Constraint": { - "contype": "CONSTR_NOTNULL" - } - }, - { - "Constraint": { - "contype": "CONSTR_PRIMARY" - } - } - ] - } - } - ], - "oncommit": "ONCOMMIT_NOOP" - } - }, - "stmt_len": 59 - } - }, - { - "RawStmt": { - "stmt": { - "CreateStmt": { - "relation": { - "relname": "users_3", - "inh": true, - "relpersistence": "p" - }, - "tableElts": [ - { - "ColumnDef": { - "colname": "id", - "typeName": { - "names": [ - { - "String": { - "str": "pg_catalog" - } - }, - { - "String": { - "str": "int4" - } - } - ], - "typemod": -1 - }, - "is_local": true, - "constraints": [ - { - "Constraint": { - "contype": "CONSTR_NOTNULL" - } - }, - { - "Constraint": { - "contype": "CONSTR_PRIMARY" - } - } - ] - } - } - ], - "oncommit": "ONCOMMIT_NOOP" - } - }, - "stmt_len": 59 - } - }, - { - "RawStmt": { - "stmt": { - "CreateFunctionStmt": { - "replace": true, - "funcname": [ - { - "String": { - "str": "update_updated_at_column" - } - } - ], - "returnType": { - "names": [ - { - "String": { - "str": "trigger" - } - } - ], - "typemod": -1 - }, - "options": [ - { - "DefElem": { - "defname": "language", - "arg": { - "String": { - "str": "plpgsql" - } - }, - "defaction": "DEFELEM_UNSPEC" - } - }, - { - "DefElem": { - "defname": "as", - "arg": { - "List": { - "items": [ - { - "String": { - "str": "\nbegin\n new.updated_at = NOW();\n return new;\nend;\n" - } - } - ] - } - }, - "defaction": "DEFELEM_UNSPEC" - } - } - ] - } - }, - "stmt_len": 171 - } - }, - { - "RawStmt": { - "stmt": { - "CreateTrigStmt": { - "trigname": "update_users_3_modtime", - "relation": { - "relname": "users_3", - "inh": true, - "relpersistence": "p" - }, - "funcname": [ - { - "String": { - "str": "update_updated_at_column" - } - } - ], - "row": true, - "timing": 2, - "events": 16 - } - }, - "stmt_len": 190 - } - }, - { - "RawStmt": { - "stmt": { - "CreateTrigStmt": { - "trigname": "update_users_1_modtime", - "relation": { - "relname": "users_1", - "inh": true, - "relpersistence": "p" - }, - "funcname": [ - { - "String": { - "str": "update_updated_at_column" - } - } - ], - "row": true, - "timing": 2, - "events": 16 - } - }, - "stmt_len": 126 - } - }, - { - "RawStmt": { - "stmt": { - "CreateTrigStmt": { - "trigname": "update_users_2_modtime", - "relation": { - "relname": "users_2", - "inh": true, - "relpersistence": "p" - }, - "funcname": [ - { - "String": { - "str": "update_updated_at_column" - } - } - ], - "row": true, - "timing": 2, - "events": 16 - } - }, - "stmt_len": 148 - } - } -] \ No newline at end of file diff --git a/sample/definitions/function/static_error_disabled.pgsql b/sample/definitions/trigger/static_error_disabled.pgsql similarity index 75% rename from sample/definitions/function/static_error_disabled.pgsql rename to sample/definitions/trigger/static_error_disabled.pgsql index 10f858b..6f9c1a0 100644 --- a/sample/definitions/function/static_error_disabled.pgsql +++ b/sample/definitions/trigger/static_error_disabled.pgsql @@ -14,8 +14,11 @@ begin end; $function$; --- TODO some kind of flag for disabling static analysis only per statement -- plpgsql-language-server:disable-static +create trigger update_users_2_modtime_disabled -- error silenced + before update on users_2 for each row + execute function update_updated_at_column (); + create trigger update_users_2_modtime -- should raise error before update on users_2 for each row execute function update_updated_at_column (); diff --git a/sample/definitions/trigger/static_error_disabled.pgsql.json b/sample/definitions/trigger/static_error_disabled.pgsql.json new file mode 100644 index 0000000..8bc91f8 --- /dev/null +++ b/sample/definitions/trigger/static_error_disabled.pgsql.json @@ -0,0 +1,184 @@ +[ + { + "RawStmt": { + "stmt": { + "DropStmt": { + "objects": [ + { + "List": { + "items": [ + { + "String": { + "str": "users_2" + } + } + ] + } + } + ], + "removeType": "OBJECT_TABLE", + "behavior": "DROP_CASCADE", + "missing_ok": true + } + }, + "stmt_len": 36 + } + }, + { + "RawStmt": { + "stmt": { + "CreateStmt": { + "relation": { + "relname": "users_2", + "inh": true, + "relpersistence": "p" + }, + "tableElts": [ + { + "ColumnDef": { + "colname": "id", + "typeName": { + "names": [ + { + "String": { + "str": "pg_catalog" + } + }, + { + "String": { + "str": "int4" + } + } + ], + "typemod": -1 + }, + "is_local": true, + "constraints": [ + { + "Constraint": { + "contype": "CONSTR_NOTNULL" + } + }, + { + "Constraint": { + "contype": "CONSTR_PRIMARY" + } + } + ] + } + } + ], + "oncommit": "ONCOMMIT_NOOP" + } + }, + "stmt_len": 60 + } + }, + { + "RawStmt": { + "stmt": { + "CreateFunctionStmt": { + "replace": true, + "funcname": [ + { + "String": { + "str": "update_updated_at_column" + } + } + ], + "returnType": { + "names": [ + { + "String": { + "str": "trigger" + } + } + ], + "typemod": -1 + }, + "options": [ + { + "DefElem": { + "defname": "language", + "arg": { + "String": { + "str": "plpgsql" + } + }, + "defaction": "DEFELEM_UNSPEC" + } + }, + { + "DefElem": { + "defname": "as", + "arg": { + "List": { + "items": [ + { + "String": { + "str": "\nbegin\n new.updated_at = NOW();\n return new;\nend;\n" + } + } + ] + } + }, + "defaction": "DEFELEM_UNSPEC" + } + } + ] + } + }, + "stmt_len": 171 + } + }, + { + "RawStmt": { + "stmt": { + "CreateTrigStmt": { + "trigname": "update_users_2_modtime_disabled", + "relation": { + "relname": "users_2", + "inh": true, + "relpersistence": "p" + }, + "funcname": [ + { + "String": { + "str": "update_updated_at_column" + } + } + ], + "row": true, + "timing": 2, + "events": 16 + } + }, + "stmt_len": 195 + } + }, + { + "RawStmt": { + "stmt": { + "CreateTrigStmt": { + "trigname": "update_users_2_modtime", + "relation": { + "relname": "users_2", + "inh": true, + "relpersistence": "p" + }, + "funcname": [ + { + "String": { + "str": "update_updated_at_column" + } + } + ], + "row": true, + "timing": 2, + "events": 16 + } + }, + "stmt_len": 148 + } + } +] \ No newline at end of file diff --git a/sample/definitions/function/static_error_trigger_column_does_not_exist.pgsql b/sample/definitions/trigger/static_error_trigger_column_does_not_exist.pgsql similarity index 100% rename from sample/definitions/function/static_error_trigger_column_does_not_exist.pgsql rename to sample/definitions/trigger/static_error_trigger_column_does_not_exist.pgsql diff --git a/sample/definitions/function/static_error_trigger_column_does_not_exist.pgsql.json b/sample/definitions/trigger/static_error_trigger_column_does_not_exist.pgsql.json similarity index 99% rename from sample/definitions/function/static_error_trigger_column_does_not_exist.pgsql.json rename to sample/definitions/trigger/static_error_trigger_column_does_not_exist.pgsql.json index 2adb046..bda322f 100644 --- a/sample/definitions/function/static_error_trigger_column_does_not_exist.pgsql.json +++ b/sample/definitions/trigger/static_error_trigger_column_does_not_exist.pgsql.json @@ -347,7 +347,7 @@ "events": 16 } }, - "stmt_len": 190 + "stmt_len": 148 } }, { diff --git a/server/src/postgres/queries/queryFileStaticAnalysis.ts b/server/src/postgres/queries/queryFileStaticAnalysis.ts index c7ae4e6..48cd37c 100644 --- a/server/src/postgres/queries/queryFileStaticAnalysis.ts +++ b/server/src/postgres/queries/queryFileStaticAnalysis.ts @@ -8,6 +8,7 @@ import { } from "@/postgres/parameters" import { FunctionInfo, TriggerInfo } from "@/postgres/parsers/parseFunctions" import { Settings } from "@/settings" +import { DISABLE_STATIC_VALIDATION_RE } from "@/utilities/regex" import { getLineRangeFromBuffer, getRangeFromBuffer, getTextAllRange, @@ -162,40 +163,44 @@ export async function queryFileStaticAnalysis( location: number | undefined, stmtLen?: number, ) { - rows.forEach( - (row) => { - const range = (() => { - if (location === undefined) { - return getTextAllRange(document) - } - if (stmtLen) { - return getRangeFromBuffer( - fileText, - location + 1, - location + 1 + stmtLen, - ) + rows.forEach((row) => { + const range = (() => { + if (location === undefined) { + return getTextAllRange(document) + } + if (stmtLen) { + const stmt = fileText.slice(location + 1, location + 1 + stmtLen) + if (DISABLE_STATIC_VALIDATION_RE + .test(stmt)) { + return } - const lineRange = getLineRangeFromBuffer( + return getRangeFromBuffer( fileText, - location, - row.lineno ? row.lineno - 1 : 0, + location + 1, + location + 1 + stmtLen, ) + } + const lineRange = getLineRangeFromBuffer( + fileText, + location, + row.lineno ? row.lineno - 1 : 0, + ) + + if (!lineRange) { + return getTextAllRange(document) + } + + return lineRange + })() + + if (!range) { + return + } - if (!lineRange) { - return getTextAllRange(document) - } - - return lineRange - })() - - errors.push({ - level: row.level, range, message: row.message, - }) - - } - , - ) - + errors.push({ + level: row.level, range, message: row.message, + }) + }) } } diff --git a/server/src/services/validation.test.ts b/server/src/services/validation.test.ts index bd10570..07a10fa 100644 --- a/server/src/services/validation.test.ts +++ b/server/src/services/validation.test.ts @@ -74,7 +74,7 @@ describe("Validate Tests", () => { it("TRIGGER on inexistent field", async () => { const diagnostics = await validateSampleFile( - "definitions/function/static_error_trigger_column_does_not_exist.pgsql", + "definitions/trigger/static_error_trigger_column_does_not_exist.pgsql", ) expect(diagnostics).toStrictEqual([ @@ -90,13 +90,21 @@ describe("Validate Tests", () => { ]) }) - // TODO - it.skip("static analysis disabled on invalid statement", async () => { + it("static analysis disabled on invalid statement", async () => { const diagnostics = await validateSampleFile( - "definitions/function/static_error_disabled.pgsql", + "definitions/trigger/static_error_disabled.pgsql", ) - expect(diagnostics).toStrictEqual([]) + if (!diagnostics) { + throw new Error("") + } + if (diagnostics?.length === 0) { + throw new Error("") + } + + expect(diagnostics).toHaveLength(1) + expect(diagnostics[0].message) + .toContain("record \"new\" has no field \"updated_at\"") }) it("FUNCTION column does not exists", async () => { diff --git a/server/src/utilities/regex.ts b/server/src/utilities/regex.ts index 96a90e2..1894365 100644 --- a/server/src/utilities/regex.ts +++ b/server/src/utilities/regex.ts @@ -1,3 +1,5 @@ +/* eslint-disable max-len */ + export function escapeRegex(string: string): string { return string.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&") } @@ -8,5 +10,5 @@ export const BEGIN_RE = /^([\s]*begin[\s]*;)/igm export const COMMIT_RE = /^([\s]*commit[\s]*;)/igm export const ROLLBACK_RE = /^([\s]*rollback[\s]*;)/igm -// eslint-disable-next-line max-len export const DISABLE_STATEMENT_VALIDATION_RE = /^ *-- +plpgsql-language-server:disable *$/m +export const DISABLE_STATIC_VALIDATION_RE = /^ *-- +plpgsql-language-server:disable-static *$/m