From 65b08f40e90c53774994898ae7b78c53ece3e98a Mon Sep 17 00:00:00 2001 From: mattkhan <86168986+mattkhan@users.noreply.github.com> Date: Fri, 19 Sep 2025 19:51:58 -0700 Subject: [PATCH 1/2] chore: add with_parsed_comment exhaustive attribute --- spec/example/app/resources/exhaustive_resource.rb | 1 + ...20024446_add_with_parsed_comment_to_exhaustives.rb | 11 +++++++++++ spec/example/db/schema.rb | 3 ++- spec/example/test/files/all_fields_false_schema.ts | 6 ++++++ spec/example/test/files/excluded_fields_schema.ts | 6 ++++++ spec/example/test/files/json_schema.json | 2 +- spec/example/test/files/multifile/Exhaustive.ts | 6 ++++++ spec/example/test/files/schema.ts | 6 ++++++ spec/example/test/files/test_schema.ts | 6 ++++++ 9 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 spec/example/db/migrate/20250920024446_add_with_parsed_comment_to_exhaustives.rb diff --git a/spec/example/app/resources/exhaustive_resource.rb b/spec/example/app/resources/exhaustive_resource.rb index 1571b0e..4b40721 100644 --- a/spec/example/app/resources/exhaustive_resource.rb +++ b/spec/example/app/resources/exhaustive_resource.rb @@ -43,6 +43,7 @@ class AssertedObject < Types::Object attribute :model_overridden attribute :resource_overridden attribute :with_comment + attribute :with_parsed_comment attribute :defaulted_boolean attribute :defaulted_at diff --git a/spec/example/db/migrate/20250920024446_add_with_parsed_comment_to_exhaustives.rb b/spec/example/db/migrate/20250920024446_add_with_parsed_comment_to_exhaustives.rb new file mode 100644 index 0000000..5ed370f --- /dev/null +++ b/spec/example/db/migrate/20250920024446_add_with_parsed_comment_to_exhaustives.rb @@ -0,0 +1,11 @@ +class AddWithParsedCommentToExhaustives < ActiveRecord::Migration[8.0] + def change + comment = <<~JSON + { + "description": "This is a parsed JSON comment.", + "test": 2 + } + JSON + add_column :exhaustives, :with_parsed_comment, :string, comment: + end +end diff --git a/spec/example/db/schema.rb b/spec/example/db/schema.rb index 522f826..ca52436 100644 --- a/spec/example/db/schema.rb +++ b/spec/example/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_07_30_180611) do +ActiveRecord::Schema[8.0].define(version: 2025_09_20_024446) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -60,6 +60,7 @@ t.string "with_comment", comment: "This is a comment." t.boolean "defaulted_boolean", default: false t.datetime "defaulted_at", default: -> { "now()" } + t.string "with_parsed_comment", comment: "{\n \"description\": \"This is a parsed JSON comment.\",\n \"test\": 2\n}\n" end create_table "posts", force: :cascade do |t| diff --git a/spec/example/test/files/all_fields_false_schema.ts b/spec/example/test/files/all_fields_false_schema.ts index baac37c..aeabd50 100644 --- a/spec/example/test/files/all_fields_false_schema.ts +++ b/spec/example/test/files/all_fields_false_schema.ts @@ -89,6 +89,12 @@ export type Exhaustive = { resourceOverridden: unknown; /** This is a comment. */ withComment: Maybe; + /** { + "description": "This is a parsed JSON comment.", + "test": 2 +} + */ + withParsedComment: Maybe; defaultedBoolean: boolean; defaultedAt: string; relationships: {}; diff --git a/spec/example/test/files/excluded_fields_schema.ts b/spec/example/test/files/excluded_fields_schema.ts index 1c7d891..ff885f8 100644 --- a/spec/example/test/files/excluded_fields_schema.ts +++ b/spec/example/test/files/excluded_fields_schema.ts @@ -87,6 +87,12 @@ export type Exhaustive = { resourceOverridden: unknown; /** This is a comment. */ withComment: Maybe; + /** { + "description": "This is a parsed JSON comment.", + "test": 2 +} + */ + withParsedComment: Maybe; defaultedBoolean: boolean; defaultedAt: string; relationships: {}; diff --git a/spec/example/test/files/json_schema.json b/spec/example/test/files/json_schema.json index f34888f..7c16d48 100644 --- a/spec/example/test/files/json_schema.json +++ b/spec/example/test/files/json_schema.json @@ -1 +1 @@ -{"$schema":"https://json-schema.org/draft-07/schema","title":"Schema","type":"object","properties":{"comment":{"$ref":"#/$defs/Comment"},"user":{"$ref":"#/$defs/User"},"post":{"$ref":"#/$defs/Post"},"exhaustive":{"$ref":"#/$defs/Exhaustive"}},"required":["comment","user","post","exhaustive"],"additionalProperties":false,"$defs":{"Comment":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["comments"]},"text":{"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"relationships":{"type":"object","properties":{"user":{"$ref":"#/$defs/User"},"deletedBy":{"$ref":"#/$defs/User"},"commentable":{"oneOf":[{"$ref":"#/$defs/Post"}]}},"required":["user"],"additionalProperties":false}},"required":["id","type","text","createdAt","updatedAt","relationships"],"additionalProperties":false},"User":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["users"]},"name":{"type":"string"},"role":{"enum":[{"value":"admin"},{"value":"content_creator"},{"value":"external"},{"value":"guest"},{"value":"system"}]},"relationships":{"type":"object","properties":{"comments":{"type":"array","items":{"$ref":"#/$defs/Comment"}},"posts":{"type":"array","items":{"$ref":"#/$defs/Post"}}},"required":["comments","posts"],"additionalProperties":false}},"required":["id","type","name","role","relationships"],"additionalProperties":false},"Post":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["posts"]},"description":{"type":"string"},"relationships":{"type":"object","properties":{"user":{"$ref":"#/$defs/User"},"comments":{"type":"array","items":{"$ref":"#/$defs/Comment"}},"participants":{"type":"array","items":{"$ref":"#/$defs/User"}}},"required":["user","comments","participants"],"additionalProperties":false}},"required":["id","type","description","relationships"],"additionalProperties":false},"Exhaustive":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["exhaustives"]},"assertedString":{"type":"string"},"assertedNumber":{"type":"number"},"assertedBoolean":{"type":"boolean"},"assertedNull":{"type":"null"},"assertedUnknown":{},"assertedObject":{"type":"object","properties":{"a":{"enum":["a"]},"b-dash":{"enum":[1]},"c":{"oneOf":[{"type":"string"},{"type":"null"}]},"d_optional":{"oneOf":[{"type":"string"},{"type":"null"}]}},"required":["a","b-dash","c"],"additionalProperties":false},"assertedMaybeObject":{"oneOf":[{"type":"object","properties":{"a":{"enum":["a"]},"b-dash":{"enum":[1]},"c":{"oneOf":[{"type":"string"},{"type":"null"}]},"d_optional":{"oneOf":[{"type":"string"},{"type":"null"}]}},"required":["a","b-dash","c"],"additionalProperties":false},{"type":"null"}]},"assertedArrayRecord":{"type":"array","items":{"type":"object","additionalProperties":"true"}},"assertedUnion":{"oneOf":[{"type":"string"},{"type":"number"}]},"assertedUnionArray":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]}},"withDescription":{"type":"string"},"inferredUnknown":{},"uuid":{"type":"string"},"string":{"type":"string"},"maybeString":{"type":"string"},"text":{"type":"string"},"integer":{"type":"number"},"float":{"type":"number"},"decimal":{"type":"string"},"datetime":{"type":"string"},"timestamp":{"type":"string"},"time":{"type":"string"},"date":{"type":"string"},"boolean":{"type":"boolean"},"arrayString":{"type":"array","items":{"type":"string"}},"maybeArrayString":{"oneOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"json":{"type":"object","additionalProperties":"true"},"jsonb":{"type":"object","additionalProperties":"true"},"daterange":{},"enum":{},"virtualUpcasedString":{"oneOf":[{"type":"string"},{"type":"null"}]},"loljk":{"enum":["never"]},"delegatedMaybeString":{"type":"string"},"modelOverridden":{},"resourceOverridden":{},"withComment":{"oneOf":[{"type":"string"},{"type":"null"}]},"defaultedBoolean":{"type":"boolean"},"defaultedAt":{"type":"string"}},"required":["id","type","assertedString","assertedNumber","assertedBoolean","assertedNull","assertedUnknown","assertedObject","assertedMaybeObject","assertedArrayRecord","assertedUnion","assertedUnionArray","withDescription","inferredUnknown","uuid","string","maybeString","text","integer","float","decimal","datetime","timestamp","time","date","boolean","arrayString","maybeArrayString","json","jsonb","daterange","enum","virtualUpcasedString","loljk","delegatedMaybeString","modelOverridden","resourceOverridden","withComment","defaultedBoolean","defaultedAt"],"additionalProperties":false}}} \ No newline at end of file +{"$schema":"https://json-schema.org/draft-07/schema","title":"Schema","type":"object","properties":{"comment":{"$ref":"#/$defs/Comment"},"user":{"$ref":"#/$defs/User"},"post":{"$ref":"#/$defs/Post"},"exhaustive":{"$ref":"#/$defs/Exhaustive"}},"required":["comment","user","post","exhaustive"],"additionalProperties":false,"$defs":{"Comment":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["comments"]},"text":{"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"relationships":{"type":"object","properties":{"user":{"$ref":"#/$defs/User"},"deletedBy":{"$ref":"#/$defs/User"},"commentable":{"oneOf":[{"$ref":"#/$defs/Post"}]}},"required":["user"],"additionalProperties":false}},"required":["id","type","text","createdAt","updatedAt","relationships"],"additionalProperties":false},"User":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["users"]},"name":{"type":"string"},"role":{"enum":[{"value":"admin"},{"value":"content_creator"},{"value":"external"},{"value":"guest"},{"value":"system"}]},"relationships":{"type":"object","properties":{"comments":{"type":"array","items":{"$ref":"#/$defs/Comment"}},"posts":{"type":"array","items":{"$ref":"#/$defs/Post"}}},"required":["comments","posts"],"additionalProperties":false}},"required":["id","type","name","role","relationships"],"additionalProperties":false},"Post":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["posts"]},"description":{"type":"string"},"relationships":{"type":"object","properties":{"user":{"$ref":"#/$defs/User"},"comments":{"type":"array","items":{"$ref":"#/$defs/Comment"}},"participants":{"type":"array","items":{"$ref":"#/$defs/User"}}},"required":["user","comments","participants"],"additionalProperties":false}},"required":["id","type","description","relationships"],"additionalProperties":false},"Exhaustive":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["exhaustives"]},"assertedString":{"type":"string"},"assertedNumber":{"type":"number"},"assertedBoolean":{"type":"boolean"},"assertedNull":{"type":"null"},"assertedUnknown":{},"assertedObject":{"type":"object","properties":{"a":{"enum":["a"]},"b-dash":{"enum":[1]},"c":{"oneOf":[{"type":"string"},{"type":"null"}]},"d_optional":{"oneOf":[{"type":"string"},{"type":"null"}]}},"required":["a","b-dash","c"],"additionalProperties":false},"assertedMaybeObject":{"oneOf":[{"type":"object","properties":{"a":{"enum":["a"]},"b-dash":{"enum":[1]},"c":{"oneOf":[{"type":"string"},{"type":"null"}]},"d_optional":{"oneOf":[{"type":"string"},{"type":"null"}]}},"required":["a","b-dash","c"],"additionalProperties":false},{"type":"null"}]},"assertedArrayRecord":{"type":"array","items":{"type":"object","additionalProperties":"true"}},"assertedUnion":{"oneOf":[{"type":"string"},{"type":"number"}]},"assertedUnionArray":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]}},"withDescription":{"type":"string"},"inferredUnknown":{},"uuid":{"type":"string"},"string":{"type":"string"},"maybeString":{"type":"string"},"text":{"type":"string"},"integer":{"type":"number"},"float":{"type":"number"},"decimal":{"type":"string"},"datetime":{"type":"string"},"timestamp":{"type":"string"},"time":{"type":"string"},"date":{"type":"string"},"boolean":{"type":"boolean"},"arrayString":{"type":"array","items":{"type":"string"}},"maybeArrayString":{"oneOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"json":{"type":"object","additionalProperties":"true"},"jsonb":{"type":"object","additionalProperties":"true"},"daterange":{},"enum":{},"virtualUpcasedString":{"oneOf":[{"type":"string"},{"type":"null"}]},"loljk":{"enum":["never"]},"delegatedMaybeString":{"type":"string"},"modelOverridden":{},"resourceOverridden":{},"withComment":{"oneOf":[{"type":"string"},{"type":"null"}]},"withParsedComment":{"oneOf":[{"type":"string"},{"type":"null"}]},"defaultedBoolean":{"type":"boolean"},"defaultedAt":{"type":"string"}},"required":["id","type","assertedString","assertedNumber","assertedBoolean","assertedNull","assertedUnknown","assertedObject","assertedMaybeObject","assertedArrayRecord","assertedUnion","assertedUnionArray","withDescription","inferredUnknown","uuid","string","maybeString","text","integer","float","decimal","datetime","timestamp","time","date","boolean","arrayString","maybeArrayString","json","jsonb","daterange","enum","virtualUpcasedString","loljk","delegatedMaybeString","modelOverridden","resourceOverridden","withComment","withParsedComment","defaultedBoolean","defaultedAt"],"additionalProperties":false}}} \ No newline at end of file diff --git a/spec/example/test/files/multifile/Exhaustive.ts b/spec/example/test/files/multifile/Exhaustive.ts index c82278f..252b34c 100644 --- a/spec/example/test/files/multifile/Exhaustive.ts +++ b/spec/example/test/files/multifile/Exhaustive.ts @@ -54,6 +54,12 @@ type Model = { resourceOverridden: unknown; /** This is a comment. */ withComment: Maybe; + /** { + "description": "This is a parsed JSON comment.", + "test": 2 +} + */ + withParsedComment: Maybe; defaultedBoolean: boolean; defaultedAt: string; relationships: {}; diff --git a/spec/example/test/files/schema.ts b/spec/example/test/files/schema.ts index 1606bd6..99db8ef 100644 --- a/spec/example/test/files/schema.ts +++ b/spec/example/test/files/schema.ts @@ -96,6 +96,12 @@ export type Exhaustive = { resourceOverridden: unknown; /** This is a comment. */ withComment: Maybe; + /** { + "description": "This is a parsed JSON comment.", + "test": 2 +} + */ + withParsedComment: Maybe; defaultedBoolean: boolean; defaultedAt: string; relationships: {}; diff --git a/spec/example/test/files/test_schema.ts b/spec/example/test/files/test_schema.ts index 5783098..cd9b903 100644 --- a/spec/example/test/files/test_schema.ts +++ b/spec/example/test/files/test_schema.ts @@ -93,6 +93,12 @@ export type Exhaustive = { resourceOverridden: unknown; /** This is a comment. */ withComment: Maybe; + /** { + "description": "This is a parsed JSON comment.", + "test": 2 +} + */ + withParsedComment: Maybe; defaultedBoolean: boolean; defaultedAt: string; relationships: {}; From 1c3fc678133225e0892a0155de3b908d256d9324 Mon Sep 17 00:00:00 2001 From: mattkhan <86168986+mattkhan@users.noreply.github.com> Date: Fri, 19 Sep 2025 19:53:33 -0700 Subject: [PATCH 2/2] feat: add ar_comment_to_string config --- lib/anchor/config.rb | 4 +++- lib/anchor/resource.rb | 7 ++++++- spec/example/config/initializers/anchor.rb | 8 ++++++++ spec/example/test/files/all_fields_false_schema.ts | 6 +----- spec/example/test/files/excluded_fields_schema.ts | 6 +----- spec/example/test/files/multifile/Exhaustive.ts | 6 +----- spec/example/test/files/schema.ts | 6 +----- spec/example/test/files/test_schema.ts | 6 +----- 8 files changed, 22 insertions(+), 27 deletions(-) diff --git a/lib/anchor/config.rb b/lib/anchor/config.rb index b42092e..ab459cb 100644 --- a/lib/anchor/config.rb +++ b/lib/anchor/config.rb @@ -9,7 +9,8 @@ class Config :use_type_as_schema_name, :maybe_as_union, :array_bracket_notation, - :infer_default_as_non_null + :infer_default_as_non_null, + :ar_comment_to_string def initialize @ar_column_to_type = nil @@ -22,6 +23,7 @@ def initialize @maybe_as_union = nil @array_bracket_notation = nil @infer_default_as_non_null = nil + @ar_comment_to_string = nil end end end diff --git a/lib/anchor/resource.rb b/lib/anchor/resource.rb index b5aa139..925c324 100644 --- a/lib/anchor/resource.rb +++ b/lib/anchor/resource.rb @@ -79,7 +79,12 @@ def anchor_attributes_properties(included_fields:) column = !method_defined && _model_class.try(:columns_hash).try(:[], model_method.to_s) if column type = Anchor::Types::Inference::ActiveRecord::SQL.from(column) - description ||= column.comment if Anchor.config.use_active_record_comment + unless description + description = column.comment if Anchor.config.use_active_record_comment + if description && !Anchor.config.ar_comment_to_string.nil? + description = Anchor.config.ar_comment_to_string.call(description) + end + end check_presence = type.is_a?(Anchor::Types::Maybe) && Anchor.config.use_active_record_validations if check_presence && _model_class.validators_on(model_method).any? do |v| if v.is_a?(ActiveRecord::Validations::NumericalityValidator) diff --git a/spec/example/config/initializers/anchor.rb b/spec/example/config/initializers/anchor.rb index 369a0f5..68f20ac 100644 --- a/spec/example/config/initializers/anchor.rb +++ b/spec/example/config/initializers/anchor.rb @@ -10,6 +10,14 @@ module Anchor return Types::Literal.new("never") if column.name == "loljk" Types::Inference::ActiveRecord::SQL.default_ar_column_to_type(column) } + c.ar_comment_to_string = lambda { |comment| + begin + res = JSON.parse(comment) + res["description"] + rescue JSON::ParserError + comment + end + } c.empty_relationship_type = -> { Anchor::Types::Object } end diff --git a/spec/example/test/files/all_fields_false_schema.ts b/spec/example/test/files/all_fields_false_schema.ts index aeabd50..575753e 100644 --- a/spec/example/test/files/all_fields_false_schema.ts +++ b/spec/example/test/files/all_fields_false_schema.ts @@ -89,11 +89,7 @@ export type Exhaustive = { resourceOverridden: unknown; /** This is a comment. */ withComment: Maybe; - /** { - "description": "This is a parsed JSON comment.", - "test": 2 -} - */ + /** This is a parsed JSON comment. */ withParsedComment: Maybe; defaultedBoolean: boolean; defaultedAt: string; diff --git a/spec/example/test/files/excluded_fields_schema.ts b/spec/example/test/files/excluded_fields_schema.ts index ff885f8..56626a6 100644 --- a/spec/example/test/files/excluded_fields_schema.ts +++ b/spec/example/test/files/excluded_fields_schema.ts @@ -87,11 +87,7 @@ export type Exhaustive = { resourceOverridden: unknown; /** This is a comment. */ withComment: Maybe; - /** { - "description": "This is a parsed JSON comment.", - "test": 2 -} - */ + /** This is a parsed JSON comment. */ withParsedComment: Maybe; defaultedBoolean: boolean; defaultedAt: string; diff --git a/spec/example/test/files/multifile/Exhaustive.ts b/spec/example/test/files/multifile/Exhaustive.ts index 252b34c..dc48bde 100644 --- a/spec/example/test/files/multifile/Exhaustive.ts +++ b/spec/example/test/files/multifile/Exhaustive.ts @@ -54,11 +54,7 @@ type Model = { resourceOverridden: unknown; /** This is a comment. */ withComment: Maybe; - /** { - "description": "This is a parsed JSON comment.", - "test": 2 -} - */ + /** This is a parsed JSON comment. */ withParsedComment: Maybe; defaultedBoolean: boolean; defaultedAt: string; diff --git a/spec/example/test/files/schema.ts b/spec/example/test/files/schema.ts index 99db8ef..ae16658 100644 --- a/spec/example/test/files/schema.ts +++ b/spec/example/test/files/schema.ts @@ -96,11 +96,7 @@ export type Exhaustive = { resourceOverridden: unknown; /** This is a comment. */ withComment: Maybe; - /** { - "description": "This is a parsed JSON comment.", - "test": 2 -} - */ + /** This is a parsed JSON comment. */ withParsedComment: Maybe; defaultedBoolean: boolean; defaultedAt: string; diff --git a/spec/example/test/files/test_schema.ts b/spec/example/test/files/test_schema.ts index cd9b903..b228c01 100644 --- a/spec/example/test/files/test_schema.ts +++ b/spec/example/test/files/test_schema.ts @@ -93,11 +93,7 @@ export type Exhaustive = { resourceOverridden: unknown; /** This is a comment. */ withComment: Maybe; - /** { - "description": "This is a parsed JSON comment.", - "test": 2 -} - */ + /** This is a parsed JSON comment. */ withParsedComment: Maybe; defaultedBoolean: boolean; defaultedAt: string;