Skip to content

Commit 09ada20

Browse files
authored
chore(richtext-lexical): fix unchecked indexed access, make richtext-lexical full ts strict (part 5/5) (#11132)
This PR concludes the series to make `richtext-lexical` full strict in TypeScript 🥳
1 parent 9068bda commit 09ada20

File tree

15 files changed

+73
-61
lines changed

15 files changed

+73
-61
lines changed

packages/richtext-lexical/src/features/blocks/server/validate.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ export const blockValidationHOC = (
4646

4747
const errorPathsSet = new Set<string>()
4848
for (const fieldKey in result) {
49-
if (result[fieldKey].errorPaths?.length) {
50-
for (const errorPath of result[fieldKey].errorPaths) {
49+
const fieldState = result[fieldKey]
50+
if (fieldState?.errorPaths?.length) {
51+
for (const errorPath of fieldState.errorPaths) {
5152
errorPathsSet.add(errorPath)
5253
}
5354
}

packages/richtext-lexical/src/features/link/server/validate.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ export const linkValidation = (
3838

3939
const errorPathsSet = new Set<string>()
4040
for (const fieldKey in result) {
41-
if (result[fieldKey].errorPaths?.length) {
42-
for (const errorPath of result[fieldKey].errorPaths) {
41+
const fieldState = result[fieldKey]
42+
if (fieldState?.errorPaths?.length) {
43+
for (const errorPath of fieldState.errorPaths) {
4344
errorPathsSet.add(errorPath)
4445
}
4546
}

packages/richtext-lexical/src/features/upload/server/feature.server.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type {
44
Field,
55
FieldSchemaMap,
66
FileData,
7-
FileSize,
7+
FileSizeImproved,
88
Payload,
99
TypeWithID,
1010
} from 'payload'
@@ -172,9 +172,7 @@ export const UploadFeature = createServerFeature<
172172

173173
// Iterate through each size in the data.sizes object
174174
for (const size in uploadDocument.value?.sizes) {
175-
const imageSize: {
176-
url?: string
177-
} & FileSize = uploadDocument.value?.sizes[size]
175+
const imageSize = uploadDocument.value.sizes[size] as FileSizeImproved
178176

179177
// Skip if any property of the size object is null
180178
if (
@@ -207,7 +205,8 @@ export const UploadFeature = createServerFeature<
207205
if (!node) {
208206
let allSubFields: Field[] = []
209207
for (const collection in props?.collections) {
210-
allSubFields = allSubFields.concat(props?.collections?.[collection]?.fields)
208+
const collectionFields = props.collections[collection]!.fields
209+
allSubFields = allSubFields.concat(collectionFields)
211210
}
212211
return allSubFields
213212
}
@@ -250,7 +249,7 @@ export const UploadFeature = createServerFeature<
250249
if (!collection) {
251250
return node
252251
}
253-
// @ts-expect-error
252+
// @ts-expect-error - Fix in Payload v4
254253
const id = node?.value?.id || node?.value // for backwards-compatibility
255254

256255
const populateDepth =

packages/richtext-lexical/src/features/upload/server/validate.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ export const uploadValidation = (
2121
},
2222
},
2323
}) => {
24-
const idType = payload.collections[node.relationTo].customIDType || payload.db.defaultIDType
25-
// @ts-expect-error
24+
const idType = payload.collections[node.relationTo]?.customIDType || payload.db.defaultIDType
25+
// @ts-expect-error - Fix in Payload v4
2626
const nodeID = node?.value?.id || node?.value // for backwards-compatibility
2727

2828
if (!isValidID(nodeID, idType)) {
@@ -62,8 +62,9 @@ export const uploadValidation = (
6262

6363
const errorPathsSet = new Set<string>()
6464
for (const fieldKey in result) {
65-
if (result[fieldKey].errorPaths?.length) {
66-
for (const errorPath of result[fieldKey].errorPaths) {
65+
const fieldState = result[fieldKey]
66+
if (fieldState?.errorPaths?.length) {
67+
for (const errorPath of fieldState.errorPaths) {
6768
errorPathsSet.add(errorPath)
6869
}
6970
}

packages/richtext-lexical/src/packages/@lexical/markdown/MarkdownExport.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,10 @@ export function createMarkdownExport(
4949
})
5050

5151
return (node) => {
52-
const output = []
52+
const output: string[] = []
5353
const children = (node || $getRoot()).getChildren()
5454

55-
for (let i = 0; i < children.length; i++) {
56-
const child = children[i]
55+
children.forEach((child, i) => {
5756
const result = exportTopLevelElements(
5857
child,
5958
elementTransformers,
@@ -67,12 +66,12 @@ export function createMarkdownExport(
6766
isNewlineDelimited &&
6867
i > 0 &&
6968
!isEmptyParagraph(child) &&
70-
!isEmptyParagraph(children[i - 1])
69+
!isEmptyParagraph(children[i - 1]!)
7170
? '\n'.concat(result)
7271
: result,
7372
)
7473
}
75-
}
74+
})
7675
// Ensure consecutive groups of texts are at least \n\n apart while each empty paragraph render as a newline.
7776
// Eg. ["hello", "", "", "hi", "\nworld"] -> "hello\n\n\nhi\n\nworld"
7877
return output.join('\n')
@@ -218,7 +217,7 @@ function exportTextFormat(
218217
const applied = new Set()
219218

220219
for (const transformer of textTransformers) {
221-
const format = transformer.format[0]
220+
const format = transformer.format[0]!
222221
const tag = transformer.tag
223222

224223
// dedup applied formats
@@ -237,8 +236,9 @@ function exportTextFormat(
237236

238237
// close any tags in the same order they were applied, if necessary
239238
for (let i = 0; i < unclosedTags.length; i++) {
240-
const nodeHasFormat = hasFormat(node, unclosedTags[i].format)
241-
const nextNodeHasFormat = hasFormat(nextNode, unclosedTags[i].format)
239+
const unclosedTag = unclosedTags[i]!
240+
const nodeHasFormat = hasFormat(node, unclosedTag.format)
241+
const nextNodeHasFormat = hasFormat(nextNode, unclosedTag.format)
242242

243243
// prevent adding closing tag if next sibling will do it
244244
if (nodeHasFormat && nextNodeHasFormat) {

packages/richtext-lexical/src/packages/@lexical/markdown/MarkdownImport.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export function createMarkdownImport(
5656
root.clear()
5757

5858
for (let i = 0; i < linesLength; i++) {
59-
const lineText = lines[i]
59+
const lineText = lines[i]!
6060

6161
const [imported, shiftedIndex] = $importMultiline(lines, i, byType.multilineElement, root)
6262

@@ -101,7 +101,7 @@ function $importMultiline(
101101
for (const transformer of multilineElementTransformers) {
102102
const { handleImportAfterStartMatch, regExpEnd, regExpStart, replace } = transformer
103103

104-
const startMatch = lines[startLineIndex].match(regExpStart)
104+
const startMatch = lines[startLineIndex]?.match(regExpStart)
105105
if (!startMatch) {
106106
continue // Try next transformer
107107
}
@@ -134,7 +134,7 @@ function $importMultiline(
134134

135135
// check every single line for the closing match. It could also be on the same line as the opening match.
136136
while (endLineIndex < linesLength) {
137-
const endMatch = regexpEndRegex ? lines[endLineIndex].match(regexpEndRegex) : null
137+
const endMatch = regexpEndRegex ? lines[endLineIndex]?.match(regexpEndRegex) : null
138138
if (!endMatch) {
139139
if (
140140
!isEndOptional ||
@@ -157,22 +157,23 @@ function $importMultiline(
157157
const linesInBetween: string[] = []
158158

159159
if (endMatch && startLineIndex === endLineIndex) {
160-
linesInBetween.push(lines[startLineIndex].slice(startMatch[0].length, -endMatch[0].length))
160+
linesInBetween.push(lines[startLineIndex]!.slice(startMatch[0].length, -endMatch[0].length))
161161
} else {
162162
for (let i = startLineIndex; i <= endLineIndex; i++) {
163+
const line = lines[i]!
163164
if (i === startLineIndex) {
164-
const text = lines[i].slice(startMatch[0].length)
165+
const text = line.slice(startMatch[0].length)
165166
linesInBetween.push(text) // Also include empty text
166167
} else if (i === endLineIndex && endMatch) {
167-
const text = lines[i].slice(0, -endMatch[0].length)
168+
const text = line.slice(0, -endMatch[0].length)
168169
linesInBetween.push(text) // Also include empty text
169170
} else {
170-
linesInBetween.push(lines[i])
171+
linesInBetween.push(line)
171172
}
172173
}
173174
}
174175

175-
if (replace(rootNode, null, startMatch, endMatch, linesInBetween, true) !== false) {
176+
if (replace(rootNode, null, startMatch, endMatch!, linesInBetween, true) !== false) {
176177
// Return here. This $importMultiline function is run line by line and should only process a single multiline element at a time.
177178
return [true, endLineIndex]
178179
}

packages/richtext-lexical/src/packages/@lexical/markdown/MarkdownShortcuts.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ function runElementTransformers(
5959
if (match && match[0].length === (match[0].endsWith(' ') ? anchorOffset : anchorOffset - 1)) {
6060
const nextSiblings = anchorNode.getNextSiblings()
6161
const [leadingNode, remainderNode] = anchorNode.splitText(anchorOffset)
62-
leadingNode.remove()
62+
leadingNode?.remove()
6363
const siblings = remainderNode ? [remainderNode, ...nextSiblings] : nextSiblings
6464
if (replace(parentNode, siblings, match, false) !== false) {
6565
return true
@@ -107,7 +107,7 @@ function runMultilineElementTransformers(
107107
if (match && match[0].length === (match[0].endsWith(' ') ? anchorOffset : anchorOffset - 1)) {
108108
const nextSiblings = anchorNode.getNextSiblings()
109109
const [leadingNode, remainderNode] = anchorNode.splitText(anchorOffset)
110-
leadingNode.remove()
110+
leadingNode?.remove()
111111
const siblings = remainderNode ? [remainderNode, ...nextSiblings] : nextSiblings
112112

113113
if (replace(parentNode, siblings, match, null, null, false) !== false) {
@@ -125,7 +125,7 @@ function runTextMatchTransformers(
125125
transformersByTrigger: Readonly<Record<string, Array<TextMatchTransformer>>>,
126126
): boolean {
127127
let textContent = anchorNode.getTextContent()
128-
const lastChar = textContent[anchorOffset - 1]
128+
const lastChar = textContent[anchorOffset - 1]!
129129
const transformers = transformersByTrigger[lastChar]
130130

131131
if (transformers == null) {
@@ -157,9 +157,10 @@ function runTextMatchTransformers(
157157
} else {
158158
;[, replaceNode] = anchorNode.splitText(startIndex, endIndex)
159159
}
160-
161-
replaceNode.selectNext(0, 0)
162-
transformer.replace(replaceNode, match)
160+
if (replaceNode) {
161+
replaceNode.selectNext(0, 0)
162+
transformer.replace(replaceNode, match)
163+
}
163164
return true
164165
}
165166

@@ -173,7 +174,7 @@ function $runTextFormatTransformers(
173174
): boolean {
174175
const textContent = anchorNode.getTextContent()
175176
const closeTagEndIndex = anchorOffset - 1
176-
const closeChar = textContent[closeTagEndIndex]
177+
const closeChar = textContent[closeTagEndIndex]!
177178
// Quick check if we're possibly at the end of inline markdown style
178179
const matchers = textFormatTransformers[closeChar]
179180

packages/richtext-lexical/src/packages/@lexical/markdown/MarkdownTransformers.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ const listReplace = (listType: ListType): ElementTransformer['replace'] => {
255255
}
256256
listItem.append(...children)
257257
listItem.select(0, 0)
258-
const indent = getIndent(match[1])
258+
const indent = getIndent(match[1]!)
259259
if (indent) {
260260
listItem.setIndent(indent)
261261
}
@@ -307,7 +307,7 @@ export const HEADING: ElementTransformer = {
307307
},
308308
regExp: HEADING_REGEX,
309309
replace: createBlockNode((match) => {
310-
const tag = ('h' + match[1].length) as HeadingTagType
310+
const tag = ('h' + match[1]!.length) as HeadingTagType
311311
return $createHeadingNode(tag)
312312
}),
313313
}
@@ -443,7 +443,7 @@ export function normalizeMarkdown(input: string, shouldMergeAdjacentLines: boole
443443
let nestedDeepCodeBlock = 0
444444

445445
for (let i = 0; i < lines.length; i++) {
446-
const line = lines[i]
446+
const line = lines[i]!
447447
const lastLine = sanitizedLines[sanitizedLines.length - 1]
448448

449449
// Code blocks of ```single line``` don't toggle the inCodeBlock flag
@@ -484,7 +484,7 @@ export function normalizeMarkdown(input: string, shouldMergeAdjacentLines: boole
484484
// Blocks must be separated by an empty line. Non-empty adjacent lines must be merged.
485485
if (
486486
EMPTY_OR_WHITESPACE_ONLY.test(line) ||
487-
EMPTY_OR_WHITESPACE_ONLY.test(lastLine) ||
487+
EMPTY_OR_WHITESPACE_ONLY.test(lastLine!) ||
488488
!lastLine ||
489489
HEADING_REGEX.test(lastLine) ||
490490
HEADING_REGEX.test(line) ||

packages/richtext-lexical/src/packages/@lexical/markdown/importTextFormatTransformer.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export function findOutermostTextFormatTransformer(
3232
const textFormatMatchStart: number = match.index || 0
3333
const textFormatMatchEnd = textFormatMatchStart + match[0].length
3434

35+
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
3536
const transformer: TextFormatTransformer = textFormatTransformersIndex.transformersByTag[match[1]]
3637

3738
return {
@@ -101,7 +102,9 @@ export function importTextFormatTransformer(
101102
const textContent = textNode.getTextContent()
102103

103104
// No text matches - we can safely process the text format match
104-
let nodeAfter, nodeBefore, transformedNode
105+
let nodeAfter: TextNode | undefined
106+
let nodeBefore: TextNode | undefined
107+
let transformedNode: TextNode
105108

106109
// If matching full content there's no need to run splitText and can reuse existing textNode
107110
// to update its content and apply format. E.g. for **_Hello_** string after applying bold
@@ -110,14 +113,20 @@ export function importTextFormatTransformer(
110113
transformedNode = textNode
111114
} else {
112115
if (startIndex === 0) {
113-
;[transformedNode, nodeAfter] = textNode.splitText(endIndex)
116+
;[transformedNode, nodeAfter] = textNode.splitText(endIndex) as [
117+
TextNode,
118+
TextNode | undefined,
119+
]
114120
} else {
115-
;[nodeBefore, transformedNode, nodeAfter] = textNode.splitText(startIndex, endIndex)
121+
;[nodeBefore, transformedNode, nodeAfter] = textNode.splitText(startIndex, endIndex) as [
122+
TextNode,
123+
TextNode,
124+
TextNode | undefined,
125+
]
116126
}
117127
}
118128

119-
transformedNode.setTextContent(match[2])
120-
129+
transformedNode.setTextContent(match[2]!)
121130
if (transformer) {
122131
for (const format of transformer.format) {
123132
if (!transformedNode.hasFormat(format)) {

packages/richtext-lexical/src/packages/@lexical/markdown/importTextMatchTransformer.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,9 @@ export function importFoundTextMatchTransformer(
9595
if (!transformer.replace) {
9696
return null
9797
}
98-
const potentialTransformedNode = transformer.replace(transformedNode, match)
98+
const potentialTransformedNode = transformedNode
99+
? transformer.replace(transformedNode, match)
100+
: undefined
99101

100102
return {
101103
nodeAfter,

0 commit comments

Comments
 (0)