Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/offense-end-at-eof.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/theme-check-common': patch
---

Fix the line and character of an offense that ends at the end of a file. They were one character short, or on the previous line when the file ends with a newline.
25 changes: 25 additions & 0 deletions packages/theme-check-common/src/utils/position.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { describe, expect, it } from 'vitest';
import { getPosition } from './position';

describe('Unit: getPosition', () => {
it('returns the line and character of an index', () => {
expect(getPosition('a\nbc', 3)).toEqual({ index: 3, line: 1, character: 1 });
});

it('returns the position of the end of the file', () => {
expect(getPosition('a\nbc', 4)).toEqual({ index: 4, line: 1, character: 2 });
expect(getPosition('a\n', 2)).toEqual({ index: 2, line: 1, character: 0 });
expect(getPosition('', 0)).toEqual({ index: 0, line: 0, character: 0 });
expect(getPosition('a\nbc', 9)).toEqual({ index: 9, line: 1, character: 2 });
});

it('ends a line at the \\n of a Windows line break', () => {
expect(getPosition('a\r\nbc', 2)).toEqual({ index: 2, line: 0, character: 2 });
expect(getPosition('a\r\nbc', 4)).toEqual({ index: 4, line: 1, character: 1 });
expect(getPosition('a\r\n', 3)).toEqual({ index: 3, line: 1, character: 0 });
});

it('returns -1 for a negative index', () => {
expect(getPosition('a\nbc', -1)).toEqual({ index: -1, line: -1, character: -1 });
});
});
23 changes: 16 additions & 7 deletions packages/theme-check-common/src/utils/position.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import lineColumn from 'line-column';
import { Position } from '../types';

/**
* The line and character of an index, both 0-based. An index past the end of
* the source is at its end, and a negative one, like the end of an unclosed
* block, has line and character -1.
*/
export function getPosition(source: string, index: number): Position {
const lineCol = lineColumn(source, { origin: 0 }).fromIndex(Math.min(index, source.length - 1));

return {
index,
line: lineCol ? lineCol.line : -1,
character: lineCol ? lineCol.col : -1,
};
if (index < 0) return { index, line: -1, character: -1 };
// Not lineColumn's fromIndex, which has no position for the end of the file,
// and splits the whole source on every call.
const end = Math.min(index, source.length);
let line = 0;
let lineStart = 0;
for (let i = source.indexOf('\n'); i !== -1 && i < end; i = source.indexOf('\n', i + 1)) {
line++;
lineStart = i + 1;
}
return { index, line, character: end - lineStart };
}

export function getOffset(source: string, line: number, column: number): number {
Expand Down
Loading