From fd8eb0236c44fa7f8132aa4e21614993837d9613 Mon Sep 17 00:00:00 2001 From: Puma7 <88577358+Puma7@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:50:01 +0200 Subject: [PATCH 1/4] fix(liquid): preserve Shopify editor section dependencies --- CHANGELOG.md | 2 + .../liquid-shopify-editor-regressions.test.ts | 95 +++++++++++++++++++ src/extraction/liquid-extractor.ts | 62 ++++++++---- 3 files changed, 140 insertions(+), 19 deletions(-) create mode 100644 __tests__/liquid-shopify-editor-regressions.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c288aa6e2..ef3293dbd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Fixes +- Shopify editor-generated JSON templates and section groups now keep their section dependencies, layout group tags link to the JSON group, and schemas inside Liquid comments or raw text no longer appear as active definitions. (#2494) + - In VB.NET, every member of a `Structure` is now indexed, including its fields, properties, methods, constructors and nested enums. Before, only the first member was, so the rest could not be found and their callers looked empty. - In VB.NET and C#, what a property's `Get` and `Set` code calls, creates and reads now belongs to that property, as do C#'s `get => …` accessors and `=> …` property bodies. Before, it was dropped, so a method used only from a property looked unused. - In VB.NET, a field or property initializer like `= Compute()` or `As New List(Of Order)` now links what it calls and creates, and so do a `Custom Event`'s `AddHandler`, `RemoveHandler` and `RaiseEvent` blocks. Re-index VB.NET and C# projects after upgrading. diff --git a/__tests__/liquid-shopify-editor-regressions.test.ts b/__tests__/liquid-shopify-editor-regressions.test.ts new file mode 100644 index 0000000000..c80eddc1e2 --- /dev/null +++ b/__tests__/liquid-shopify-editor-regressions.test.ts @@ -0,0 +1,95 @@ +import { describe, it, expect } from 'vitest'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { LiquidExtractor } from '../src/extraction/liquid-extractor'; +import { CodeGraph } from '../src/index'; + +const extract = (file: string, source: string) => new LiquidExtractor(file, source).extract(); +const refs = (file: string, source: string) => extract(file, source).unresolvedReferences; + +describe('Shopify editor JSON and section groups', () => { + it.each(['\n', '\r\n'])('reads generated comments and reports type positions with %j', (newline) => { + const source = [ + '/* IMPORTANT: This file is generated by the Shopify editor. */', + '{', + ' "sections": {', + ' "main": {', + ' "type": "main-product",', + ' "settings": { "text": "https://host/path/*literal*/" }', + ' },', + ' "duplicate": { "type": "main-product" }', + ' },', + ' "order": ["main", "duplicate"]', + '}', + ].join(newline); + expect(refs('templates/product.json', source).map(r => [r.referenceName, r.line, r.column])) + .toEqual([['sections/main-product.liquid', 5, 14]]); + }); + + it.each([ + ['templates/customers/login.json', 'main-login'], + ['sections/header-group.json', 'header'], + ])('reads comments in %s', (file, type) => { + const source = `/* Generated */\n{"sections":{"main":{"type":"${type}"}},"order":["main"]}`; + expect(refs(file, source).map(r => r.referenceName)).toEqual([`sections/${type}.liquid`]); + }); + + it.each(['null', '[]', '{', '{"sections":[]}', '{"sections":{"main":null}}', + '{"sections":{"main":{"type":17}}}', '{"sections":{"main":{"type":"header"}}', + '{"sections":{"main":{"type":"header"}},}', + '/* unfinished {"sections":{"main":{"type":"header"}}}'])('does not invent dependencies from %s', source => { + expect(refs('templates/product.json', source)).toEqual([]); + }); + + it.each([ + "{% sections 'header-group' %}", + '{%- sections "header-group" -%}', + "{% liquid\n sections 'header-group'\n%}", + ])('links a section group to JSON: %s', source => { + expect(refs('layout/theme.liquid', source).map(r => r.referenceName)) + .toEqual(['sections/header-group.json']); + }); + + it('keeps singular sections distinct from groups of the same name', () => { + const result = extract('layout/theme.liquid', "{% section 'header' %}{% sections 'header' %}"); + expect(result.unresolvedReferences.map(r => r.referenceName)) + .toEqual(['sections/header.liquid', 'sections/header.json']); + expect(new Set(result.nodes.map(n => n.id)).size).toBe(result.nodes.length); + }); + + it.each(['comment', 'raw'])('ignores schemas and group references inside %s', tag => { + const source = `{% ${tag} %}{% sections 'ghost' %}{% schema %}{"name":"Ghost"}{% endschema %}{% end${tag} %}\n` + + '{% schema %}{"name":"Live"}{% endschema %}'; + const result = extract('sections/header.liquid', source); + expect(result.unresolvedReferences).toEqual([]); + expect(result.nodes.filter(n => n.kind === 'constant').map(n => [n.name, n.startLine])) + .toEqual([['Live', 2]]); + }); + + it('resolves the complete layout to group to section to snippet chain', async () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-shopify-editor-')); + let cg: CodeGraph | undefined; + try { + for (const dir of ['layout', 'sections', 'snippets', 'config']) fs.mkdirSync(path.join(root, dir)); + const files = { + 'config/settings_schema.json': '[]', + 'layout/theme.liquid': "{% sections 'header-group' %}", + 'sections/header-group.json': '/* Generated */\n{"type":"header","name":"Header","sections":{"main":{"type":"header"}},"order":["main"]}', + 'sections/header.liquid': "{% render 'logo' %}", + 'snippets/logo.liquid': 'Logo', + }; + for (const [file, source] of Object.entries(files)) fs.writeFileSync(path.join(root, file), source); + cg = CodeGraph.initSync(root); + await cg.indexAll(); + cg.resolveReferences(); + expect(cg.getFileDependencies('layout/theme.liquid')).toEqual(['sections/header-group.json']); + expect(cg.getFileDependencies('sections/header-group.json')).toEqual(['sections/header.liquid']); + expect(cg.getFileDependencies('sections/header.liquid')).toEqual(['snippets/logo.liquid']); + expect(cg.getFileDependents('sections/header.liquid')).toEqual(['sections/header-group.json']); + } finally { + cg?.close(); + fs.rmSync(root, { recursive: true, force: true }); + } + }); +}); diff --git a/src/extraction/liquid-extractor.ts b/src/extraction/liquid-extractor.ts index f6d9aee60a..9f60dd2969 100644 --- a/src/extraction/liquid-extractor.ts +++ b/src/extraction/liquid-extractor.ts @@ -1,12 +1,13 @@ import { Node, Edge, ExtractionResult, ExtractionError, UnresolvedReference } from '../types'; import { generateNodeId, NodeIdAllocator } from './tree-sitter-helpers'; +import { parseTree, findNodeAtLocation, ParseError } from 'jsonc-parser'; /** * LiquidExtractor - Extracts relationships from Liquid template files * * Liquid is a templating language (used by Shopify, Jekyll, etc.) that doesn't * have traditional functions or classes. Instead, we extract: - * - Section references ({% section 'name' %}) + * - Section and section-group references ({% section/sections 'name' %}) * - Snippet references ({% render 'name' %} and {% include 'name' %}) * - Schema blocks ({% schema %}...{% endschema %}) */ @@ -102,25 +103,29 @@ export class LiquidExtractor { * used only from a JSON template (the OS 2.0 norm) is no longer orphaned. */ private extractShopifyJsonSections(fromNodeId: string): void { - let parsed: unknown; - try { - parsed = JSON.parse(this.source); - } catch { - return; // not valid JSON (or a partial) — nothing to link - } - const sections = (parsed as { sections?: Record })?.sections; - if (!sections || typeof sections !== 'object') return; + // Shopify's editor prepends a comment to generated templates and groups. + // Parse with offsets, but reject incomplete input rather than invent links. + const errors: ParseError[] = []; + const parsed = parseTree(this.source, errors, { allowTrailingComma: false }); + if (errors.length || parsed?.type !== 'object') return; + const sections = findNodeAtLocation(parsed, ['sections']); + if (sections?.type !== 'object') return; const seen = new Set(); - for (const key of Object.keys(sections)) { - const type = sections[key]?.type; - if (typeof type !== 'string' || seen.has(type)) continue; + for (const property of sections.children ?? []) { + const value = property.children?.[1]; + if (value?.type !== 'object') continue; + const typeNode = findNodeAtLocation(value, ['type']); + if (typeNode?.type !== 'string') continue; + const type = typeNode.value as string; + if (seen.has(type)) continue; seen.add(type); + const line = this.getLineNumber(typeNode.offset); this.unresolvedReferences.push({ fromNodeId, referenceName: `sections/${type}.liquid`, referenceKind: 'references', - line: 1, - column: 0, + line, + column: typeNode.offset - this.getLineStart(line), }); } } @@ -280,12 +285,13 @@ export class LiquidExtractor { } /** - * Extract {% section 'name' %} references + * Extract {% section 'name' %} and {% sections 'group' %} references */ private extractSectionReferences(fileNodeId: string): void { // Both spellings, as for render/include above. - for (const match of this.findTagOccurrences('section', `['"]([^'"]+)['"]`)) { + for (const match of this.findTagOccurrences('section|sections', `['"]([^'"]+)['"]`)) { const fullMatch = match.fullMatch; + const tagType = match.groups[0]; const sectionName = match.groups[1]; const line = this.getLineNumber(match.index); @@ -315,13 +321,13 @@ export class LiquidExtractor { }); // Create a component node for the section reference - const nodeId = this.nodeIds.generate(this.filePath, 'component', `section:${sectionName}`, line, match.index - this.getLineStart(line)); + const nodeId = this.nodeIds.generate(this.filePath, 'component', `${tagType}:${sectionName}`, line, match.index - this.getLineStart(line)); const node: Node = { id: nodeId, kind: 'component', name: sectionName!, - qualifiedName: `${this.filePath}::section:${sectionName}`, + qualifiedName: `${this.filePath}::${tagType}:${sectionName}`, filePath: this.filePath, language: 'liquid', startLine: line, @@ -343,7 +349,7 @@ export class LiquidExtractor { // Add unresolved reference to the section file this.unresolvedReferences.push({ fromNodeId: fileNodeId, - referenceName: `sections/${sectionName}.liquid`, + referenceName: `sections/${sectionName}.${tagType === 'sections' ? 'json' : 'liquid'}`, referenceKind: 'references', line, column: match.index - this.getLineStart(line), @@ -357,9 +363,27 @@ export class LiquidExtractor { private extractSchema(fileNodeId: string): void { // Match {% schema %}...{% endschema %} const schemaRegex = /\{%[-]?\s*schema\s*[-]?%\}([\s\S]*?)\{%[-]?\s*endschema\s*[-]?%\}/g; + // Schema-looking examples inside non-executing text are not definitions. + const suppressedRanges: [number, number][] = []; + const tags = /\{%[-]?\s*(\w+|#)([\s\S]*?)[-]?%\}/g; + let suppressed: { name: string; start: number } | undefined; + let tag: RegExpExecArray | null; + while ((tag = tags.exec(this.source)) !== null) { + if (suppressed) { + if (tag[1] === `end${suppressed.name}`) { + suppressedRanges.push([suppressed.start, tags.lastIndex]); + suppressed = undefined; + } + } else if (tag[1] === 'comment' || tag[1] === 'raw') { + suppressed = { name: tag[1], start: tag.index }; + } + } + if (suppressed) suppressedRanges.push([suppressed.start, this.source.length]); let match; while ((match = schemaRegex.exec(this.source)) !== null) { + const index = match.index; + if (suppressedRanges.some(([start, end]) => index >= start && index < end)) continue; const [fullMatch, schemaContent] = match; const startLine = this.getLineNumber(match.index); const endLine = this.getLineNumber(match.index + fullMatch.length); From 388c275638189b8052a7bd0b0a10df17251271db Mon Sep 17 00:00:00 2001 From: Puma7 <88577358+Puma7@users.noreply.github.com> Date: Sun, 11 Oct 2026 20:22:05 +0200 Subject: [PATCH 2/4] test(liquid): guard non-Shopify JSON and theme isolation --- .../liquid-shopify-editor-regressions.test.ts | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/__tests__/liquid-shopify-editor-regressions.test.ts b/__tests__/liquid-shopify-editor-regressions.test.ts index c80eddc1e2..8f57ad51a0 100644 --- a/__tests__/liquid-shopify-editor-regressions.test.ts +++ b/__tests__/liquid-shopify-editor-regressions.test.ts @@ -58,6 +58,28 @@ describe('Shopify editor JSON and section groups', () => { expect(new Set(result.nodes.map(n => n.id)).size).toBe(result.nodes.length); }); + it('preserves strict JSON section dependencies and gives their actual positions', () => { + const source = '{\n "sections": { "main": { "type": "header" } },\n "order": ["main"]\n}'; + expect(refs('templates/index.json', source).map(r => [r.referenceName, r.line, r.column])) + .toEqual([['sections/header.liquid', 2, 34]]); + }); + + it('preserves standard Liquid assignments and suppresses commented/raw assignments', () => { + const source = "{% assign keep = 'hello' %}\n{% comment %}{% assign hidden = 1 %}{% endcomment %}\n" + + "{% raw %}{% assign hidden2 = 2 %}{% endraw %}\n{% assign after = 'world' %}"; + const result = extract('views/page.liquid', source); + expect(result.nodes.filter(n => n.kind === 'variable').map(n => [n.name, n.startLine])) + .toEqual([['keep', 1], ['after', 4]]); + expect(result.unresolvedReferences).toEqual([]); + }); + + it.each([ + "{% # sections 'ghost' %}", + "{% liquid\n # sections 'ghost'\n%}", + ])('does not infer group dependencies from inline comments: %s', source => { + expect(refs('layout/theme.liquid', source)).toEqual([]); + }); + it.each(['comment', 'raw'])('ignores schemas and group references inside %s', tag => { const source = `{% ${tag} %}{% sections 'ghost' %}{% schema %}{"name":"Ghost"}{% endschema %}{% end${tag} %}\n` + '{% schema %}{"name":"Live"}{% endschema %}'; @@ -92,4 +114,50 @@ describe('Shopify editor JSON and section groups', () => { fs.rmSync(root, { recursive: true, force: true }); } }); + + it('leaves commented JSON in a non-Shopify project outside the Liquid index', async () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-non-shopify-json-')); + let cg: CodeGraph | undefined; + try { + for (const dir of ['templates', 'sections', 'views']) fs.mkdirSync(path.join(root, dir)); + fs.writeFileSync(path.join(root, 'templates/page.json'), '/* CMS data */\n{"sections":{"main":{"type":"hero"}}}'); + fs.writeFileSync(path.join(root, 'sections/hero.liquid'), '

{{ title }}

'); + fs.writeFileSync(path.join(root, 'views/page.liquid'), "{% assign title = 'Hello' %}"); + cg = CodeGraph.initSync(root); + await cg.indexAll(); + cg.resolveReferences(); + expect(cg.getNodesByKind('file').map(n => n.filePath)).not.toContain('templates/page.json'); + expect(cg.getFileDependents('sections/hero.liquid')).toEqual([]); + expect(cg.getNodesByKind('variable').some(n => n.name === 'title')).toBe(true); + } finally { + cg?.close(); + fs.rmSync(root, { recursive: true, force: true }); + } + }); + + it('keeps group references within their own theme even when a group is missing', async () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-group-isolation-')); + let cg: CodeGraph | undefined; + try { + const files = { + 'first/layout/theme.liquid': "{% sections 'header-group' %}", + 'second/layout/theme.liquid': "{% sections 'header-group' %}", + 'second/sections/header-group.json': '/* Generated */\n{"type":"header","name":"Header","sections":{"main":{"type":"header"}},"order":["main"]}', + 'second/sections/header.liquid': '
Header
', + }; + for (const [file, source] of Object.entries(files)) { + fs.mkdirSync(path.dirname(path.join(root, file)), { recursive: true }); + fs.writeFileSync(path.join(root, file), source); + } + cg = CodeGraph.initSync(root); + await cg.indexAll(); + cg.resolveReferences(); + expect(cg.getFileDependencies('first/layout/theme.liquid')).toEqual([]); + expect(cg.getFileDependencies('second/layout/theme.liquid')).toEqual(['second/sections/header-group.json']); + expect(cg.getFileDependencies('second/sections/header-group.json')).toEqual(['second/sections/header.liquid']); + } finally { + cg?.close(); + fs.rmSync(root, { recursive: true, force: true }); + } + }); }); From 44fcf86c86d2808b2a6c3d7353903a85e7ede205 Mon Sep 17 00:00:00 2001 From: Puma7 <88577358+Puma7@users.noreply.github.com> Date: Sun, 11 Oct 2026 20:42:15 +0000 Subject: [PATCH 3/4] docs(liquid): one changelog line per Shopify fix, and the resolver comment names section groups The #2494 entry bundled three fixes into one sentence and left a blank line that turned the whole Fixes list loose. It is now three plain-language bullets next to the other Shopify entries. The Shopify theme resolver's header comment now says the extractor also records `sections/.json` for `{% sections %}`. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019zFDanXAMbewuhGttMdGh5 --- CHANGELOG.md | 5 +++-- src/resolution/shopify-themes.ts | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef3293dbd9..52ae8135e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,8 +14,6 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Fixes -- Shopify editor-generated JSON templates and section groups now keep their section dependencies, layout group tags link to the JSON group, and schemas inside Liquid comments or raw text no longer appear as active definitions. (#2494) - - In VB.NET, every member of a `Structure` is now indexed, including its fields, properties, methods, constructors and nested enums. Before, only the first member was, so the rest could not be found and their callers looked empty. - In VB.NET and C#, what a property's `Get` and `Set` code calls, creates and reads now belongs to that property, as do C#'s `get => …` accessors and `=> …` property bodies. Before, it was dropped, so a method used only from a property looked unused. - In VB.NET, a field or property initializer like `= Compute()` or `As New List(Of Order)` now links what it calls and creates, and so do a `Custom Event`'s `AddHandler`, `RemoveHandler` and `RaiseEvent` blocks. Re-index VB.NET and C# projects after upgrading. @@ -76,6 +74,9 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - JSON files in a `templates` or `sections` folder are now read as Shopify Liquid only inside a Shopify theme, where the folder that contains `templates` or `sections` also has `layout/theme.liquid` or `config/settings_schema.json`. Before, all such JSON was, so a .NET project template's `template.json`, a schematic or a CMS's content showed up in the index as an empty Liquid file, and one that lists `sections` could be linked to an unrelated Liquid file of the same name. Shopify themes, including one kept in a subfolder of a repository, are indexed as before, and the next sync removes JSON that an earlier version indexed this way. - In a repository that holds more than one Shopify theme, a theme's `{% render %}`, `{% include %}` and `{% section %}` tags and the sections its JSON templates list now link only to that theme's own snippets and sections, which is where Shopify looks for them. Before, a section or snippet the theme doesn't have was linked to another theme's file of the same name, and a theme kept in a subfolder could link to the root theme's copy instead of its own. A reference the theme can't satisfy now links to nothing. Projects with a single theme are indexed as before. Re-index projects with several Shopify themes after upgrading. - `codegraph sync` and the file watcher now link a Shopify section or snippet to the files that name it when it is added after them, or comes back after being deleted, whether they name it in a `{% render %}`, `{% include %}` or `{% section %}` tag or as a section `type` in a JSON template. Before, the link appeared only once a file naming it changed or the project was re-indexed, even in a repository with a single theme, so the new section or snippet looked unused and `codegraph affected` missed the files that use it. Re-index Shopify themes after upgrading to pick up links an earlier version missed. +- In a Shopify theme, a JSON template or section group that starts with the comment the theme editor writes at the top now links the sections it lists, at the line of each section's `type`. Before, such a file linked nothing, so most sections of an editor-saved theme looked unused. Re-index Shopify themes after upgrading. (#2494) +- In a Shopify theme layout, `{% sections 'header-group' %}` now links to the section group `sections/header-group.json`. Before, only the singular `{% section %}` tag was linked. (#2494) +- A `{% schema %}` written inside `{% comment %}` or `{% raw %}` in a Liquid file is no longer indexed as a definition. (#2494) - `codegraph sync` and the file watcher now link a route to the page or layout it loads lazily when that file is added after the router was indexed, comes back after being deleted, or gets the component the route renders in a later edit, whether or not the import names the file's extension: React Router's `lazy: () => import('./pages/Team')`, Vue Router's `component: () => import('@/views/Login')` and Angular's `loadComponent: () => import('./home/home.component')`. Before, the route stayed unlinked until the router file changed or the project was re-indexed, so the page looked unused and `codegraph affected` missed the tests that reach it through the router. Re-index after upgrading to pick up links an earlier version missed. - A file named the way Google names its tests, like `wire_format_unittest.cc` in protocolbuffers/protobuf, `logging_unittest.cc` in google/glog or a Python `foo_unittest.py`, now counts as a test, as `wire_format_test.cc` already did. Before, it was taken for production code: search and `codegraph_explore` ranked it alongside the code it tests, `codegraph affected` never listed it, and calls in production code could link to a class or type a unittest declares for itself, as google/breakpad's `stack_frame_entries_.size()` did to a unittest's `StackHelper`. Re-index projects that have such files after upgrading. - A file in a `unittests/` folder, where llvm/llvm-project and google/breakpad keep their unit tests, or in a folder named like a unittest, such as `dcheck_unittest/` in google/glog, now counts as a test whatever its own name says, like LLVM's `CountCopyAndMove.h` helper. Before, such a file was taken for production code unless its name said test, and none of them were kept apart from the program the way a `tests/` folder is: search and `codegraph_explore` ranked them alongside production code, `codegraph affected` missed them, and production code linked to a unit test's own declarations, as LLVM's `Ctx` and `OpInfo` variables did to ones its unit tests declare. A folder named just `unittest/`, like CPython's `Lib/unittest/`, is still production code. Re-index projects that have such folders after upgrading. diff --git a/src/resolution/shopify-themes.ts b/src/resolution/shopify-themes.ts index 3c2f3c8b5c..16b72b6320 100644 --- a/src/resolution/shopify-themes.ts +++ b/src/resolution/shopify-themes.ts @@ -4,9 +4,10 @@ * `{% render 'price' %}` renders the `snippets/price.liquid` of the theme the * file belongs to; `{% section 'header' %}` and a JSON template's section * `"type"` name a file in that theme's `sections/`. Shopify never looks in - * another theme. The Liquid extractor records these as `snippets/.liquid` - * and `sections/.liquid`, and the path matcher took any file whose path - * ends with that, so in a repository holding several themes (a theme tool's + * another theme. The Liquid extractor records these as `snippets/.liquid`, + * `sections/.liquid` and, for a `{% sections %}` group, + * `sections/.json`, and the path matcher took any file whose path ends + * with that, so in a repository holding several themes (a theme tool's * examples, a store's seasonal themes) a theme missing a section linked to * another theme's copy of it. * From dc8fb4b2e1bf607a52987c41a1053c74cc10c78f Mon Sep 17 00:00:00 2001 From: Puma7 <88577358+Puma7@users.noreply.github.com> Date: Tue, 13 Oct 2026 00:01:48 +0200 Subject: [PATCH 4/4] Normalize changelog line endings without changing content --- CHANGELOG.md | 2878 +++++++++++++++++++++++++------------------------- 1 file changed, 1439 insertions(+), 1439 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e7d75bc5d..5a6c8015e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,1439 +1,1439 @@ -# Changelog - -All notable changes to CodeGraph are documented here. Each entry also ships as -a [GitHub Release](https://github.com/colbymchenry/codegraph/releases) tagged -`vX.Y.Z`, which is where most people will look. - -Each release opens with a short **Highlights** list — the handful of things most -users will notice — followed by the full notes. - -This project follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) -and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -### Fixes - -- In a Shopify theme, a JSON template or section group that starts with the comment the theme editor writes at the top now links the sections it lists, at the line of each section's `type`. Before, such a file linked nothing, so most sections of an editor-saved theme looked unused. Re-index Shopify themes after upgrading. (#2494) -- In a Shopify theme layout, `{% sections 'header-group' %}` now links to the section group `sections/header-group.json`. Before, only the singular `{% section %}` tag was linked. (#2494) -- A `{% schema %}` written inside `{% comment %}` or `{% raw %}` in a Liquid file is no longer indexed as a definition. (#2494) -- In VB.NET, every member of a `Structure` is now indexed, including its fields, properties, methods, constructors and nested enums. Before, only the first member was, so the rest could not be found and their callers looked empty. -- In VB.NET and C#, what a property's `Get` and `Set` code calls, creates and reads now belongs to that property, as do C#'s `get => …` accessors and `=> …` property bodies. Before, it was dropped, so a method used only from a property looked unused. -- In VB.NET, a field or property initializer like `= Compute()` or `As New List(Of Order)` now links what it calls and creates, and so do a `Custom Event`'s `AddHandler`, `RemoveHandler` and `RaiseEvent` blocks. Re-index VB.NET and C# projects after upgrading. -- In C#, a field or property initializer like `private readonly ILogger _log = LogManager.GetLogger(typeof(X));` or `public List Items { get; } = new();` now links what it calls, creates and reads, including inside a lambda, and a target-typed `new()` there counts as creating the declared type. Before, initializers were skipped, so a method or class used only from one — like a converter created in a static list — looked unused, and a method passed as a value there was credited to the whole class instead of the field or property. -- In C# and VB.NET, a constant no longer stands in for a type with the same name: `new Station { … }` links to class `Station` even when another class declares `private const string Station`, `new Version(…)` no longer links to a `const string Version`, and a field like `static readonly Meter Meter` no longer points at itself. Thanks @EvanYu1980 for the report and @drakeo338. (#2337) -- In VB.NET, a call on a variable, parameter, field or property now reaches a method of the type it is declared with — one that type inherits, or an extension method written for it — and nothing when that type comes from outside your project, instead of any project method that merely shares the name. -- In VB.NET, a call to one of .NET's own methods such as `Add`, `Contains`, `Clear` or `Dispose` on a value whose type isn't known is no longer linked to a project method of that name, unless the value is named after that method's class. -- In VB.NET, a type name is looked up the way VB.NET does it — through the namespaces around it, the file's and project's `Imports` (aliases included), and the caller's own project — so a class declared in several namespaces or projects no longer draws every call to whichever copy was indexed first, and two candidates nothing tells apart get no link at all. -- In VB.NET, a call with no receiver, or on `Me`, now reaches only a member of the class it is written in, of a class it inherits, or of a Module, and no longer a nearby class's member of the same name. -- VB.NET designer code such as `New System.Drawing.Point(…)` or `Me.Size = New System.Drawing.Size(…)` is no longer linked to a project type or member that only shares the name, and a class nested inside another is matched by its bare name only from inside that class or one that inherits it. -- Upgrading CodeGraph while an agent session is open no longer leaves the old version's background server in charge of your project: the first session started from the new install stops it and starts a current one in its place, even while sessions opened before the upgrade are still running. That old server could no longer load the language parsers the upgrade removed, so it saved every file it re-indexed with no symbols, while new sessions could only read the index beside it without keeping it up to date. A background server from a newer install is never stopped, and sessions opened before the upgrade keep the old version until you restart them. Thanks @lipchey for the report. (#2335) -- A file is no longer saved with no symbols when its language parser can't be loaded, which is what happened to every file a background server re-indexed after an upgrade removed its install: the file keeps what it had and is indexed again once the parser loads, and files an earlier version emptied this way are re-indexed by the next sync. A background server also exits on its own once its install is upgraded or removed, so the next session starts one from the current install. Thanks @lipchey for the report. (#2335) -- `codegraph status` no longer says the index is up to date while indexed files are missing their symbols: it now names files the parser couldn't read and files stored without their symbols (which `codegraph sync` repairs), `status --json` counts both, and `codegraph files --json` lists each file's recorded errors. Thanks @lipchey for the report. (#2336) -- Indexing large Python projects is much faster again and needs less memory: since 1.6.2, resolving Python references re-read source files over and over, so a project the size of CPython took several times as long to index. The graph it builds is unchanged. Thanks @bompus for the report. (#2332) -- In VB.NET, reading or setting a `Shared` field or property through its class or module name, like `AppSession.SessionId`, `AppSession.CurrentUser = "demo"` or `AppSession.Items(0)`, now counts as a use of that member and of the class, and so do reading an `Enum` value like `Mode.Fast` and calling a `Shared` function without parentheses. Before, only calls written with parentheses were linked, so `codegraph callers` on such a field, property or class came back empty and impact missed most of the code that depends on it. A local, parameter or field that only shares a class's name is not mistaken for the class. Thanks @serkanince for the report and @ChrisPrapas. (#2305) -- In Vue and Nuxt components, calls written in the template (`{{ formatDate(post) }}`, `:to="getAccountRoute(account)"`, `@click="save(item)"`) and calls in a destructuring declaration at the top of `