Skip to content

Commit 61bafc7

Browse files
authored
Merge pull request #42525 from github/repo-sync
Repo sync
2 parents f0c0a69 + 511825e commit 61bafc7

File tree

4 files changed

+63
-67
lines changed

4 files changed

+63
-67
lines changed

content/billing/concepts/product-billing/github-copilot-licenses.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ There are several ways to use {% data variables.product.prodname_copilot_short %
4747
* You must choose a monthly or yearly billing cycle and provide a payment method.
4848
* If you do not cancel before the end of the trial, it automatically converts to a paid plan.
4949
* You can cancel any time during the 30 days. If you cancel, you will not be charged and will keep access until the trial ends.
50+
* Free trials are limited to three per payment method. Additional trials will continue as paid subscriptions.
5051

5152
### Educational and open source benefits
5253

src/languages/lib/render-with-fallback.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,27 @@ import type { Context } from '@/types'
55

66
export class EmptyTitleError extends Error {}
77

8-
interface LiquidToken {
8+
export interface LiquidToken {
99
file?: string
1010
getPosition?: () => [number, number]
1111
}
1212

13-
interface LiquidError extends Error {
13+
/**
14+
* Custom error class for Liquid rendering errors with proper type safety.
15+
* Use this instead of creating Error objects and mutating them with type assertions.
16+
*
17+
* @example
18+
* const error = new LiquidError('Unknown tag', 'ParseError')
19+
* error.token = { file: '/content/test.md', getPosition: () => [1, 5] }
20+
*/
21+
export class LiquidError extends Error {
1422
token?: LiquidToken
1523
originalError?: Error
24+
25+
constructor(message: string, name: 'ParseError' | 'RenderError' | 'TokenizationError') {
26+
super(message)
27+
this.name = name
28+
}
1629
}
1730

1831
interface RenderOptions {

src/languages/scripts/count-translation-corruptions.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,19 @@ function run(languageCode: string, site: Site, englishReusables: Reusables) {
7777
const illegalTags = new Map<string, number>()
7878

7979
function countError(error: TokenizationError, where: string) {
80-
const originalError = (error as { originalError?: Error }).originalError
80+
// TokenizationError from liquidjs may have originalError and token.content
81+
// but these aren't in the public type definitions
82+
const errorWithExtras = error as TokenizationError & {
83+
originalError?: Error
84+
token?: { content?: string }
85+
}
86+
const originalError = errorWithExtras.originalError
8187
const errorString = originalError ? originalError.message : error.message
82-
if (errorString.includes('illegal tag syntax')) {
83-
const illegalTag = (error as unknown as { token: { content: string } }).token.content
84-
illegalTags.set(illegalTag, (illegalTags.get(illegalTag) || 0) + 1)
88+
if (errorString.includes('illegal tag syntax') && errorWithExtras.token?.content) {
89+
illegalTags.set(
90+
errorWithExtras.token.content,
91+
(illegalTags.get(errorWithExtras.token.content) || 0) + 1,
92+
)
8593
}
8694
errors.set(errorString, (errors.get(errorString) || 0) + 1)
8795
wheres.set(where, (wheres.get(where) || 0) + 1)

src/languages/tests/translation-error-comments.ts

Lines changed: 35 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,11 @@ import {
44
EmptyTitleError,
55
renderContentWithFallback,
66
executeWithFallback,
7+
LiquidError,
78
} from '../lib/render-with-fallback'
89
import { TitleFromAutotitleError } from '@/content-render/unified/rewrite-local-links'
910
import Page from '@/frame/lib/page'
1011

11-
// Type aliases for error objects with token information
12-
type ErrorWithToken = Error & { token: { file: string; getPosition: () => number[] } }
13-
type ErrorWithTokenNoFile = Error & { token: { getPosition: () => number[] } }
14-
type ErrorWithTokenNoPosition = Error & { token: { file: string } }
15-
type ErrorWithTokenAndOriginal = Error & {
16-
token: { file: string; getPosition: () => number[] }
17-
originalError: Error
18-
}
19-
2012
describe('Translation Error Comments', () => {
2113
// Mock renderContent for integration tests
2214
let mockRenderContent: MockedFunction<
@@ -35,9 +27,8 @@ describe('Translation Error Comments', () => {
3527
describe('createTranslationFallbackComment', () => {
3628
describe('Liquid ParseError', () => {
3729
test('includes all fields when token information is available', () => {
38-
const error = new Error("Unknown tag 'badtag', line:1, col:3")
39-
error.name = 'ParseError'
40-
;(error as unknown as ErrorWithToken).token = {
30+
const error = new LiquidError("Unknown tag 'badtag', line:1, col:3", 'ParseError')
31+
error.token = {
4132
file: '/content/test/article.md',
4233
getPosition: () => [1, 3],
4334
}
@@ -57,15 +48,15 @@ describe('Translation Error Comments', () => {
5748

5849
describe('Liquid RenderError', () => {
5950
test('includes original error message when available', () => {
60-
const error = new Error("Unknown variable 'variables.nonexistent.value'")
61-
error.name = 'RenderError'
62-
;(error as unknown as ErrorWithToken).token = {
51+
const error = new LiquidError(
52+
"Unknown variable 'variables.nonexistent.value'",
53+
'RenderError',
54+
)
55+
error.token = {
6356
file: '/content/test/intro.md',
6457
getPosition: () => [3, 15],
6558
}
66-
;(error as unknown as ErrorWithTokenAndOriginal).originalError = new Error(
67-
'Variable not found: variables.nonexistent.value',
68-
)
59+
error.originalError = new Error('Variable not found: variables.nonexistent.value')
6960

7061
const result = createTranslationFallbackComment(error, 'rawIntro')
7162

@@ -78,9 +69,8 @@ describe('Translation Error Comments', () => {
7869
})
7970

8071
test('falls back to main error message when no originalError', () => {
81-
const error = new Error('Main error message')
82-
error.name = 'RenderError'
83-
;(error as unknown as ErrorWithToken).token = {
72+
const error = new LiquidError('Main error message', 'RenderError')
73+
error.token = {
8474
file: '/content/test.md',
8575
getPosition: () => [1, 1],
8676
}
@@ -93,9 +83,8 @@ describe('Translation Error Comments', () => {
9383

9484
describe('Liquid TokenizationError', () => {
9585
test('includes tokenization error details', () => {
96-
const error = new Error('Unexpected token, line:1, col:10')
97-
error.name = 'TokenizationError'
98-
;(error as unknown as ErrorWithToken).token = {
86+
const error = new LiquidError('Unexpected token, line:1, col:10', 'TokenizationError')
87+
error.token = {
9988
file: '/content/test/page.md',
10089
getPosition: () => [1, 10],
10190
}
@@ -147,9 +136,8 @@ describe('Translation Error Comments', () => {
147136

148137
describe('Error handling edge cases', () => {
149138
test('handles error with no token information gracefully', () => {
150-
const error = new Error('Generic liquid error without token info')
151-
error.name = 'RenderError'
152-
// No token property
139+
const error = new LiquidError('Generic liquid error without token info', 'RenderError')
140+
// No token property set
153141

154142
const result = createTranslationFallbackComment(error, 'rawIntro')
155143

@@ -163,9 +151,8 @@ describe('Translation Error Comments', () => {
163151
})
164152

165153
test('handles error with token but no file', () => {
166-
const error = new Error('Error message')
167-
error.name = 'ParseError'
168-
;(error as unknown as ErrorWithTokenNoFile).token = {
154+
const error = new LiquidError('Error message', 'ParseError')
155+
error.token = {
169156
// No file property
170157
getPosition: () => [5, 10],
171158
}
@@ -178,9 +165,8 @@ describe('Translation Error Comments', () => {
178165
})
179166

180167
test('handles error with token but no getPosition method', () => {
181-
const error = new Error('Error message')
182-
error.name = 'ParseError'
183-
;(error as unknown as ErrorWithTokenNoPosition).token = {
168+
const error = new LiquidError('Error message', 'ParseError')
169+
error.token = {
184170
file: '/content/test.md',
185171
// No getPosition method
186172
}
@@ -194,8 +180,7 @@ describe('Translation Error Comments', () => {
194180

195181
test('truncates very long error messages', () => {
196182
const longMessage = 'A'.repeat(300) // Very long error message
197-
const error = new Error(longMessage)
198-
error.name = 'ParseError'
183+
const error = new LiquidError(longMessage, 'ParseError')
199184

200185
const result = createTranslationFallbackComment(error, 'rawTitle')
201186

@@ -211,8 +196,7 @@ describe('Translation Error Comments', () => {
211196
})
212197

213198
test('properly escapes quotes in error messages', () => {
214-
const error = new Error('Error with "double quotes" and more')
215-
error.name = 'RenderError'
199+
const error = new LiquidError('Error with "double quotes" and more', 'RenderError')
216200

217201
const result = createTranslationFallbackComment(error, 'rawTitle')
218202

@@ -233,9 +217,7 @@ describe('Translation Error Comments', () => {
233217
})
234218

235219
test('handles error with no message', () => {
236-
const error = new Error()
237-
error.name = 'ParseError'
238-
// Message will be empty string by default
220+
const error = new LiquidError('', 'ParseError')
239221

240222
const result = createTranslationFallbackComment(error, 'title')
241223

@@ -245,8 +227,7 @@ describe('Translation Error Comments', () => {
245227
})
246228

247229
test('cleans up multiline messages', () => {
248-
const error = new Error('Line 1\nLine 2\n Line 3 \n\nLine 5')
249-
error.name = 'RenderError'
230+
const error = new LiquidError('Line 1\nLine 2\n Line 3 \n\nLine 5', 'RenderError')
250231

251232
const result = createTranslationFallbackComment(error, 'content')
252233

@@ -257,9 +238,8 @@ describe('Translation Error Comments', () => {
257238

258239
describe('Comment format validation', () => {
259240
test('comment format is valid HTML', () => {
260-
const error = new Error('Test error')
261-
error.name = 'ParseError'
262-
;(error as unknown as ErrorWithToken).token = {
241+
const error = new LiquidError('Test error', 'ParseError')
242+
error.token = {
263243
file: '/content/test.md',
264244
getPosition: () => [1, 1],
265245
}
@@ -275,9 +255,8 @@ describe('Translation Error Comments', () => {
275255
})
276256

277257
test('contains all required fields when available', () => {
278-
const error = new Error('Detailed error message')
279-
error.name = 'RenderError'
280-
;(error as unknown as ErrorWithToken).token = {
258+
const error = new LiquidError('Detailed error message', 'RenderError')
259+
error.token = {
281260
file: '/content/detailed-test.md',
282261
getPosition: () => [42, 15],
283262
}
@@ -294,9 +273,8 @@ describe('Translation Error Comments', () => {
294273
})
295274

296275
test('maintains consistent field order', () => {
297-
const error = new Error('Test message')
298-
error.name = 'ParseError'
299-
;(error as unknown as ErrorWithToken).token = {
276+
const error = new LiquidError('Test message', 'ParseError')
277+
error.token = {
300278
file: '/content/test.md',
301279
getPosition: () => [1, 1],
302280
}
@@ -336,9 +314,8 @@ describe('Translation Error Comments', () => {
336314
mockRenderContent.mockImplementation(
337315
(template: string, innerContext: Record<string, unknown>) => {
338316
if (innerContext.currentLanguage !== 'en' && template.includes('badtag')) {
339-
const error = new Error("Unknown tag 'badtag'")
340-
error.name = 'ParseError'
341-
;(error as unknown as ErrorWithToken).token = {
317+
const error = new LiquidError("Unknown tag 'badtag'", 'ParseError')
318+
error.token = {
342319
file: '/content/test.md',
343320
getPosition: () => [1, 5],
344321
}
@@ -375,8 +352,7 @@ describe('Translation Error Comments', () => {
375352
mockRenderContent.mockImplementation(
376353
(template: string, innerContext: Record<string, unknown>) => {
377354
if (innerContext.currentLanguage !== 'en' && template.includes('badtag')) {
378-
const error = new Error("Unknown tag 'badtag'")
379-
error.name = 'ParseError'
355+
const error = new LiquidError("Unknown tag 'badtag'", 'ParseError')
380356
throw error
381357
}
382358
return 'English Title'
@@ -399,9 +375,8 @@ describe('Translation Error Comments', () => {
399375
}
400376

401377
const failingCallable = async () => {
402-
const error = new Error("Unknown variable 'variables.bad'")
403-
error.name = 'RenderError'
404-
;(error as unknown as ErrorWithToken).token = {
378+
const error = new LiquidError("Unknown variable 'variables.bad'", 'RenderError')
379+
error.token = {
405380
file: '/content/article.md',
406381
getPosition: () => [10, 20],
407382
}
@@ -427,8 +402,7 @@ describe('Translation Error Comments', () => {
427402
}
428403

429404
const failingCallable = async () => {
430-
const error = new Error('Test error')
431-
error.name = 'RenderError'
405+
const error = new LiquidError('Test error', 'RenderError')
432406
throw error
433407
}
434408

0 commit comments

Comments
 (0)