migrate-goldmark-extension-v1-to-v2
Description
This skill helps you migrate a goldmark (https://github.com/yuin/goldmark) extension from version 1 to version 2. It provides guidance on the changes needed to update your project to be compatible with the new version of goldmark.
Knowledges
- CommonMark key points : List of key points of CommonMark spec that you should be aware of when implementing a goldmark extension.
- Breaking changes in v2 : List of breaking changes in goldmark v2 that you should be aware of when migrating your extension from v1 to v2.
- How to create an extension : Guide on how to create a goldmark extension in v2, including the new extension pattern and how to implement parser and renderer extensions.
Migration steps
Overview of the migration process
- Create a migration plan for the extension.
- MUST ask human to confirm that the migration plan is acceptable before proceeding with the migration.
- MUST ask human to how to test the extension after migration before proceeding with the migration.
- e.g. : "How do you want to test the extension after migration? Do you have any test cases or examples that you want to use for testing?"
- Execute the migration plan to update the extension code to be compatible with goldmark v2.
- Update the test cases to ensure that the extension works as expected with goldmark v2.
- Test the extension with goldmark v2 to ensure that it works as expected. If there are any issues, fix them and re-test until the extension works as expected.
- Update the documentation to reflect any changes made during the migration process.
Create a migration plan
Task
- Make sure you have read and understood the Breaking changes in v2 document.
- Make sure you have read and understood the How to create an extension document.
- You create a ./features/goldmark-migration-plan.md file that contains a migration plan for the extension.
Key points to consider when migrating your extension
Extension options
- If the extension uses "unified" options for both parser and renderer, they should be split into separate options for each.
- e.g. :
- v1
type Option interface { myOption() } type ParserOption interface { Option applyParserOption(*parserConfig) } type RendererOption interface { Option applyRendererOption(*rendererConfig) } func New(opts ...Option) goldmark.Extender { // takes unified options // ... } - v2
type ParserOption interface { applyParserOption(*parserConfig) } type HTMLRendererOption interface { // explicitly named for **HTML** applyRendererOption(*htmlRendererConfig) // you can access the shared renderer config like `XHTML` or `Unsafe` in the renderer config } func NewParser(opts ...ParserOption) parser.Extension { // takes parser options // ... } var Parser = NewParser() // Default instance of parser extension func NewHTMLRenderer(opts ...HTMLRendererOption) html.Extension { // takes renderer options // ... } var HTMLRenderer = NewHTMLRenderer() // Default instance of renderer extension
- v1
- e.g. :
AST nodes
- use
text.Value(single line),text.MultiLineValue(multi-line) instead of[]bytefor values that can be parsed from source text in inline AST nodes.- In your parser, you must choose
text.Decoderimplementation to decode the source valuetext.IdentityDecoder: for raw contents like inline HTMLs, inline code, etc.reader.Decoder: other contents like text, links, etc. This decoder decodes entity references,\escapes, etc.
- In most cases, you will choose
text.Decoder. DO NOT usetext.IdentityDecoderunless you have a clear intention to do so.
- In your parser, you must choose
- use
text.Linesinstead of[]text.Segmentfor values in block AST nodes that have raw contents like HTML blocks, code blocks, etc. - Properties in AST Dump should be
text.Valueas possible.- e.g.
- OK:
// Dump implements Node.Dump. func (n *Text) Dump(_ []byte) *NodeDump { m := map[string]any{ "Value": n.Value, // text.Value } fs := textFlagsString(n.flags) if len(fs) != 0 { m["Flags"] = fs } return NewNodeDump(n, m) } - Not OK:
// Dump implements Node.Dump. func (n *Text) Dump(source []byte) *NodeDump { m := map[string]any{ "Value": n.Value.Str(source), // string } fs := textFlagsString(n.flags) if len(fs) != 0 { m["Flags"] = fs } return NewNodeDump(n, m) }
- OK:
- e.g.
- In v2, attribute values are
text.Valuewhich has almost the same specification as HTML attributes.- Therefore, if the project were using non-string attributes in v1, human must decide on one of the following policies:
- Use the
goldmark_v1_attributebuild tag to continue using v1 attributes as they are. - Convert attribute values to strings to comply with the v2 specification.
- Use the
- MUST ask human to decide on one of the above policies before proceeding with the migration.
- Therefore, if the project were using non-string attributes in v1, human must decide on one of the following policies:
Parsing
- In v2, all nodes have a start position. goldmark/v2 automatically sets the start position to the node. However, if you want to customize the start position, you need to call
SetPosappropriately.
HTML Rendering
text.Valueandtext.Linescan be rendered using theWriteTomethod whenever possible. Also, the output destination ofWriteToshould usehtml.ContextHTMLWriter(rc)orhtml.ContextTextWriter(rc).- e.g. :
tw := html.ContextTextWriter(rc) _, _ = n.Value.WriteTo(tw, source) WriteTois fast because it does not allocate new memory. On the other hand, if you writeValuedirectly liketw.Write(n.Value.Value(source)), it may copy the contents ofValue, which can degrade performance.
- e.g. :
Recommended naming convention(for public stuff)
- Use
myext.NewParser()andmyext.NewHTMLRenderer()for the extension constructors.- e.g. :
metaextensionmeta.NewParser(),meta.NewHTMLRenderer()
- e.g. :
- Use
myext.Parserandmyext.HTMLRendereras the default extension values.- e.g. :
var Parser = NewParser(),var HTMLRenderer = NewHTMLRenderer()
- e.g. :
- Use
myext.ParserOptionandmyext.HTMLRendererOptionfor functional options.- e.g. :
type ParseOption func(*parserConfig),type HTMLRendererOption func(*htmlRendererConfig)
- e.g. :
Execute migration plan
- Make sure you are on a branch that is not
mainormaster. User must create a new branch like 'v2' to work on the migration before using this skill.- If you are on
mainormaster, STOP this skill and ask human to create a new branch like 'v2' to work on the migration.
- If you are on
- Make sure
go.modfile is updated to usegithub.com/yuin/goldmark/v2instead ofgithub.com/yuin/goldmark. User must addgoldmark/v2before using this skill.- If
go.modfile is not updated, STOP this skill and ask human to updatego.modfile to usegithub.com/yuin/goldmark/v2instead ofgithub.com/yuin/goldmark.
- If
- Update the module path in your
go.modfile with new major version. For example, changegithub.com/you/yourextensiontogithub.com/you/yourextension/v2.- MUST ask human to make sure that the module path is updated in
go.modfile before proceeding with the migration.- If human confirms that the module path is updated, proceed with the migration, otherwise, STOP this skill and ask human to update the module path in
go.modfile with new major version.
- If human confirms that the module path is updated, proceed with the migration, otherwise, STOP this skill and ask human to update the module path in
- MUST ask human to make sure that the module path is updated in