|
| 1 | +--- |
| 2 | +name: migrate-goldmark-extension-v1-to-v2 |
| 3 | +context: fork |
| 4 | +description: Migrate goldmark extension from goldmark v1 to v2. |
| 5 | +allowed-tools: Bash Read |
| 6 | +--- |
| 7 | + |
| 8 | +# migrate-goldmark-extension-v1-to-v2 |
| 9 | +## Description |
| 10 | +This skill helps you migrate a goldmark (https://github.com/yuin/goldmark) extension from version 1 to version 2. It |
| 11 | +provides guidance on the changes needed to update your project to be compatible with the new version of goldmark. |
| 12 | + |
| 13 | +## Knowledges |
| 14 | + |
| 15 | +- [CommonMark key points](../../references/commonmark-key-points.md) : List of key points of CommonMark spec that you should be aware of when implementing a goldmark extension. |
| 16 | +- [Breaking changes in v2](../../references/breaking-changes-in-v2.md) : List of breaking changes in goldmark v2 that you should be aware of when migrating your extension from v1 to v2. |
| 17 | +- [How to create an extension](./references/how-to-create-an-extension.md) : Guide on how to create a goldmark extension in v2, including the new extension pattern and how to implement parser and renderer extensions. |
| 18 | + |
| 19 | +## Migration steps |
| 20 | +### Overview of the migration process |
| 21 | + |
| 22 | +- Create a migration plan for the extension. |
| 23 | +- **MUST** ask human to confirm that the migration plan is acceptable before proceeding with the migration. |
| 24 | +- **MUST** ask human to how to test the extension after migration before proceeding with the migration. |
| 25 | + - 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?" |
| 26 | +- Execute the migration plan to update the extension code to be compatible with goldmark v2. |
| 27 | +- Update the test cases to ensure that the extension works as expected with goldmark v2. |
| 28 | +- 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. |
| 29 | +- Update the documentation to reflect any changes made during the migration process. |
| 30 | + |
| 31 | +### Create a migration plan |
| 32 | +#### Task |
| 33 | + |
| 34 | +- Make sure you have read and understood the [Breaking changes in v2](../../references/breaking-changes-in-v2.md) document. |
| 35 | +- Make sure you have read and understood the [How to create an extension](./references/how-to-create-an-extension.md) document. |
| 36 | +- You create a ./features/goldmark-migration-plan.md file that contains a migration plan for the extension. |
| 37 | + |
| 38 | +#### Key points to consider when migrating your extension |
| 39 | +##### Extension options |
| 40 | + |
| 41 | +- If the extension uses "unified" options for both parser and renderer, they should be split into separate options for each. |
| 42 | + - e.g. : |
| 43 | + - v1 |
| 44 | + ```go |
| 45 | + type Option interface { |
| 46 | + myOption() |
| 47 | + } |
| 48 | + |
| 49 | + type ParserOption interface { |
| 50 | + Option |
| 51 | + applyParserOption(*parserConfig) |
| 52 | + } |
| 53 | + |
| 54 | + type RendererOption interface { |
| 55 | + Option |
| 56 | + applyRendererOption(*rendererConfig) |
| 57 | + } |
| 58 | + |
| 59 | + func New(opts ...Option) goldmark.Extender { // takes unified options |
| 60 | + // ... |
| 61 | + } |
| 62 | + ``` |
| 63 | + - v2 |
| 64 | + ```go |
| 65 | + type ParserOption interface { |
| 66 | + applyParserOption(*parserConfig) |
| 67 | + } |
| 68 | +
|
| 69 | + type HTMLRendererOption interface { // explicitly named for **HTML** |
| 70 | + applyRendererOption(*htmlRendererConfig) // you can access the shared renderer config like `XHTML` or `Unsafe` in the renderer config |
| 71 | + } |
| 72 | +
|
| 73 | + func NewParser(opts ...ParserOption) parser.Extension { // takes parser options |
| 74 | + // ... |
| 75 | + } |
| 76 | +
|
| 77 | + var Parser = NewParser() // Default instance of parser extension |
| 78 | +
|
| 79 | + func NewHTMLRenderer(opts ...HTMLRendererOption) html.Extension { // takes renderer options |
| 80 | + // ... |
| 81 | + } |
| 82 | +
|
| 83 | + var HTMLRenderer = NewHTMLRenderer() // Default instance of renderer extension |
| 84 | + ``` |
| 85 | + |
| 86 | +##### AST nodes |
| 87 | + |
| 88 | +- use `text.Value`(single line), `text.MultiLineValue`(multi-line) instead of `[]byte` for values that can be parsed from source text in inline AST nodes. |
| 89 | + - In your parser, you must choose `text.Decoder` implementation to decode the source value |
| 90 | + - `text.IdentityDecoder` : for raw contents like inline HTMLs, inline code, etc. |
| 91 | + - `reader.Decoder` : other contents like text, links, etc. This decoder decodes entity references, `\` escapes, etc. |
| 92 | + - In most cases, you will choose `text.Decoder`. **DO NOT** use `text.IdentityDecoder` unless you have a clear intention to do so. |
| 93 | +- use `text.Lines` instead of `[]text.Segment` for values in block AST nodes that have **raw contents** like HTML blocks, code blocks, etc. |
| 94 | +- Properties in AST Dump should be `text.Value` as possible. |
| 95 | + - e.g. |
| 96 | + - OK: |
| 97 | + ``` |
| 98 | + // Dump implements Node.Dump. |
| 99 | + func (n *Text) Dump(_ []byte) *NodeDump { |
| 100 | + m := map[string]any{ |
| 101 | + "Value": n.Value, // text.Value |
| 102 | + } |
| 103 | + fs := textFlagsString(n.flags) |
| 104 | + if len(fs) != 0 { |
| 105 | + m["Flags"] = fs |
| 106 | + } |
| 107 | + return NewNodeDump(n, m) |
| 108 | + } |
| 109 | + ``` |
| 110 | + - Not OK: |
| 111 | + ``` |
| 112 | + // Dump implements Node.Dump. |
| 113 | + func (n *Text) Dump(source []byte) *NodeDump { |
| 114 | + m := map[string]any{ |
| 115 | + "Value": n.Value.Str(source), // string |
| 116 | + } |
| 117 | + fs := textFlagsString(n.flags) |
| 118 | + if len(fs) != 0 { |
| 119 | + m["Flags"] = fs |
| 120 | + } |
| 121 | + return NewNodeDump(n, m) |
| 122 | + } |
| 123 | + ``` |
| 124 | +- In v2, attribute values are `text.Value` which has almost the same specification as HTML attributes. |
| 125 | + - Therefore, if the project were using non-string attributes in v1, human must decide on one of the following policies: |
| 126 | + - Use the `goldmark_v1_attribute` build tag to continue using v1 attributes as they are. |
| 127 | + - Convert attribute values to strings to comply with the v2 specification. |
| 128 | + - **MUST** ask human to decide on one of the above policies before proceeding with the migration. |
| 129 | + |
| 130 | +##### Parsing |
| 131 | + |
| 132 | +- 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 `SetPos` appropriately. |
| 133 | + |
| 134 | +##### HTML Rendering |
| 135 | + |
| 136 | +- `text.Value` and `text.Lines` can be rendered using the `WriteTo` method whenever possible. Also, the output destination of `WriteTo` should use `html.ContextHTMLWriter(rc)` or `html.ContextTextWriter(rc)`. |
| 137 | + - e.g. : |
| 138 | + ```go |
| 139 | + tw := html.ContextTextWriter(rc) |
| 140 | + _, _ = n.Value.WriteTo(tw, source) |
| 141 | + ``` |
| 142 | + - `WriteTo` is fast because it does not allocate new memory. On the other hand, if you write `Value` directly like `tw.Write(n.Value.Value(source))`, it may copy the contents of `Value`, which can degrade performance. |
| 143 | + |
| 144 | +##### Recommended naming convention(for public stuff) |
| 145 | + |
| 146 | +- Use `myext.NewParser()` and `myext.NewHTMLRenderer()` for the extension constructors. |
| 147 | + - e.g. : `meta` extension |
| 148 | + - `meta.NewParser()`, `meta.NewHTMLRenderer()` |
| 149 | +- Use `myext.Parser` and `myext.HTMLRenderer` as the default extension values. |
| 150 | + - e.g. : `var Parser = NewParser()`, `var HTMLRenderer = NewHTMLRenderer()` |
| 151 | +- Use `myext.ParserOption` and `myext.HTMLRendererOption` for functional options. |
| 152 | + - e.g. : `type ParseOption func(*parserConfig)`, `type HTMLRendererOption func(*htmlRendererConfig)` |
| 153 | + |
| 154 | +### Execute migration plan |
| 155 | +- Make sure you are on a branch that is not `main` or `master`. User must create a new branch like 'v2' to work on the migration before using this skill. |
| 156 | + - If you are on `main` or `master`, **STOP** this skill and ask human to create a new branch like 'v2' to work on the migration. |
| 157 | +- Make sure `go.mod` file is updated to use `github.com/yuin/goldmark/v2` instead of `github.com/yuin/goldmark`. User must add `goldmark/v2` before using this skill. |
| 158 | + - If `go.mod` file is not updated, **STOP** this skill and ask human to update `go.mod` file to use `github.com/yuin/goldmark/v2` instead of `github.com/yuin/goldmark`. |
| 159 | +- Update the module path in your `go.mod` file with new major version. For example, change `github.com/you/yourextension` to `github.com/you/yourextension/v2`. |
| 160 | + - **MUST** ask human to make sure that the module path is updated in `go.mod` file before proceeding with the migration. |
| 161 | + - 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.mod` file with new major version. |
| 162 | + |
0 commit comments