Skip to content

Commit 5a53854

Browse files
authored
chore(richtext-lexical): fix unchecked indexed access (part 4) (#11048)
1 parent ac6f4e2 commit 5a53854

File tree

8 files changed

+85
-75
lines changed

8 files changed

+85
-75
lines changed

packages/richtext-lexical/src/features/link/client/plugins/autoLink/index.tsx

Lines changed: 45 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ export function createLinkMatcherWithRegExp(
5353
}
5454

5555
function findFirstMatch(text: string, matchers: LinkMatcher[]): LinkMatcherResult | null {
56-
for (let i = 0; i < matchers.length; i++) {
57-
const match = matchers[i](text)
56+
for (const matcher of matchers) {
57+
const match = matcher(text)
5858

5959
if (match != null) {
6060
return match
@@ -66,8 +66,8 @@ function findFirstMatch(text: string, matchers: LinkMatcher[]): LinkMatcherResul
6666

6767
const PUNCTUATION_OR_SPACE = /[.,;\s]/
6868

69-
function isSeparator(char: string): boolean {
70-
return PUNCTUATION_OR_SPACE.test(char)
69+
function isSeparator(char: string | undefined): boolean {
70+
return char !== undefined && PUNCTUATION_OR_SPACE.test(char)
7171
}
7272

7373
function endsWithSeparator(textContent: string): boolean {
@@ -124,13 +124,13 @@ function isContentAroundIsValid(
124124
nodes: TextNode[],
125125
): boolean {
126126
const contentBeforeIsValid =
127-
matchStart > 0 ? isSeparator(text[matchStart - 1]) : isPreviousNodeValid(nodes[0])
127+
matchStart > 0 ? isSeparator(text[matchStart - 1]) : isPreviousNodeValid(nodes[0]!)
128128
if (!contentBeforeIsValid) {
129129
return false
130130
}
131131

132132
const contentAfterIsValid =
133-
matchEnd < text.length ? isSeparator(text[matchEnd]) : isNextNodeValid(nodes[nodes.length - 1])
133+
matchEnd < text.length ? isSeparator(text[matchEnd]) : isNextNodeValid(nodes[nodes.length - 1]!)
134134
return contentAfterIsValid
135135
}
136136

@@ -153,7 +153,7 @@ function extractMatchingNodes(
153153
const currentNodes = [...nodes]
154154

155155
while (currentNodes.length > 0) {
156-
const currentNode = currentNodes[0]
156+
const currentNode = currentNodes[0]!
157157
const currentNodeText = currentNode.getTextContent()
158158
const currentNodeLength = currentNodeText.length
159159
const currentNodeStart = currentOffset
@@ -187,22 +187,22 @@ function $createAutoLinkNode_(
187187

188188
const linkNode = $createAutoLinkNode({ fields })
189189
if (nodes.length === 1) {
190-
let remainingTextNode = nodes[0]
191-
let linkTextNode
192-
if (startIndex === 0) {
193-
;[linkTextNode, remainingTextNode] = remainingTextNode.splitText(endIndex)
194-
} else {
195-
;[, linkTextNode, remainingTextNode] = remainingTextNode.splitText(startIndex, endIndex)
190+
const split = (
191+
startIndex === 0 ? nodes[0]?.splitText(endIndex) : nodes[0]?.splitText(startIndex, endIndex)
192+
)!
193+
194+
const [linkTextNode, remainingTextNode] = split
195+
if (linkTextNode) {
196+
const textNode = $createTextNode(match.text)
197+
textNode.setFormat(linkTextNode.getFormat())
198+
textNode.setDetail(linkTextNode.getDetail())
199+
textNode.setStyle(linkTextNode.getStyle())
200+
linkNode.append(textNode)
201+
linkTextNode.replace(linkNode)
196202
}
197-
const textNode = $createTextNode(match.text)
198-
textNode.setFormat(linkTextNode.getFormat())
199-
textNode.setDetail(linkTextNode.getDetail())
200-
textNode.setStyle(linkTextNode.getStyle())
201-
linkNode.append(textNode)
202-
linkTextNode.replace(linkNode)
203203
return remainingTextNode
204204
} else if (nodes.length > 1) {
205-
const firstTextNode = nodes[0]
205+
const firstTextNode = nodes[0]!
206206
let offset = firstTextNode.getTextContent().length
207207
let firstLinkTextNode
208208
if (startIndex === 0) {
@@ -212,8 +212,7 @@ function $createAutoLinkNode_(
212212
}
213213
const linkNodes: LexicalNode[] = []
214214
let remainingTextNode
215-
for (let i = 1; i < nodes.length; i++) {
216-
const currentNode = nodes[i]
215+
nodes.forEach((currentNode) => {
217216
const currentNodeText = currentNode.getTextContent()
218217
const currentNodeLength = currentNodeText.length
219218
const currentNodeStart = offset
@@ -223,30 +222,35 @@ function $createAutoLinkNode_(
223222
linkNodes.push(currentNode)
224223
} else {
225224
const [linkTextNode, endNode] = currentNode.splitText(endIndex - currentNodeStart)
226-
linkNodes.push(linkTextNode)
225+
if (linkTextNode) {
226+
linkNodes.push(linkTextNode)
227+
}
227228
remainingTextNode = endNode
228229
}
229230
}
230231
offset += currentNodeLength
231-
}
232-
const selection = $getSelection()
233-
const selectedTextNode = selection ? selection.getNodes().find($isTextNode) : undefined
234-
const textNode = $createTextNode(firstLinkTextNode.getTextContent())
235-
textNode.setFormat(firstLinkTextNode.getFormat())
236-
textNode.setDetail(firstLinkTextNode.getDetail())
237-
textNode.setStyle(firstLinkTextNode.getStyle())
238-
linkNode.append(textNode, ...linkNodes)
239-
// it does not preserve caret position if caret was at the first text node
240-
// so we need to restore caret position
241-
if (selectedTextNode && selectedTextNode === firstLinkTextNode) {
242-
if ($isRangeSelection(selection)) {
243-
textNode.select(selection.anchor.offset, selection.focus.offset)
244-
} else if ($isNodeSelection(selection)) {
245-
textNode.select(0, textNode.getTextContent().length)
232+
})
233+
234+
if (firstLinkTextNode) {
235+
const selection = $getSelection()
236+
const selectedTextNode = selection ? selection.getNodes().find($isTextNode) : undefined
237+
const textNode = $createTextNode(firstLinkTextNode.getTextContent())
238+
textNode.setFormat(firstLinkTextNode.getFormat())
239+
textNode.setDetail(firstLinkTextNode.getDetail())
240+
textNode.setStyle(firstLinkTextNode.getStyle())
241+
linkNode.append(textNode, ...linkNodes)
242+
// it does not preserve caret position if caret was at the first text node
243+
// so we need to restore caret position
244+
if (selectedTextNode && selectedTextNode === firstLinkTextNode) {
245+
if ($isRangeSelection(selection)) {
246+
textNode.select(selection.anchor.offset, selection.focus.offset)
247+
} else if ($isNodeSelection(selection)) {
248+
textNode.select(0, textNode.getTextContent().length)
249+
}
246250
}
251+
firstLinkTextNode.replace(linkNode)
252+
return remainingTextNode
247253
}
248-
firstLinkTextNode.replace(linkNode)
249-
return remainingTextNode
250254
}
251255
return undefined
252256
}
@@ -378,7 +382,7 @@ function replaceWithChildren(node: ElementNode): LexicalNode[] {
378382
const childrenLength = children.length
379383

380384
for (let j = childrenLength - 1; j >= 0; j--) {
381-
node.insertAfter(children[j])
385+
node.insertAfter(children[j]!)
382386
}
383387

384388
node.remove()

packages/richtext-lexical/src/features/toolbars/fixed/client/Toolbar/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ function ToolbarGroupComponent({
8585
}
8686
return
8787
}
88-
const item = activeItems[0]
88+
const item = activeItems[0]!
8989

9090
let label = item.key
9191
if (item.label) {

packages/richtext-lexical/src/features/toolbars/inline/client/Toolbar/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ function ToolbarGroupComponent({
8686
return
8787
}
8888
const item = activeItems[0]
89-
setDropdownIcon(() => item.ChildComponent)
89+
setDropdownIcon(() => item?.ChildComponent)
9090
},
9191
[group],
9292
)

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

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,19 @@ export const UploadFeature = createServerFeature<
5959
if (props.collections) {
6060
for (const collection in props.collections) {
6161
clientProps.collections[collection] = {
62-
hasExtraFields: props.collections[collection].fields.length >= 1,
62+
hasExtraFields: props.collections[collection]!.fields.length >= 1,
6363
}
6464
}
6565
}
6666

6767
const validRelationships = _config.collections.map((c) => c.slug) || []
6868

69-
for (const collection in props.collections) {
70-
if (props.collections[collection].fields?.length) {
71-
props.collections[collection].fields = await sanitizeFields({
69+
for (const collectionKey in props.collections) {
70+
const collection = props.collections[collectionKey]!
71+
if (collection.fields?.length) {
72+
collection.fields = await sanitizeFields({
7273
config: _config as unknown as Config,
73-
fields: props.collections[collection].fields,
74+
fields: collection.fields,
7475
parentIsLocalized,
7576
requireFieldLevelRichTextEditor: isRoot,
7677
validRelationships,
@@ -88,10 +89,11 @@ export const UploadFeature = createServerFeature<
8889

8990
const schemaMap: FieldSchemaMap = new Map()
9091

91-
for (const collection in props.collections) {
92-
if (props.collections[collection].fields?.length) {
93-
schemaMap.set(collection, {
94-
fields: props.collections[collection].fields,
92+
for (const collectionKey in props.collections) {
93+
const collection = props.collections[collectionKey]!
94+
if (collection.fields?.length) {
95+
schemaMap.set(collectionKey, {
96+
fields: collection.fields,
9597
})
9698
}
9799
}

packages/richtext-lexical/src/index.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
268268
originalNode: originalNodeIDMap[id],
269269
parentRichTextFieldPath: path,
270270
parentRichTextFieldSchemaPath: schemaPath,
271-
previousNode: previousNodeIDMap[id],
271+
previousNode: previousNodeIDMap[id]!,
272272
req,
273273
})
274274
}
@@ -281,9 +281,9 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
281281
if (subFieldFn && subFieldDataFn) {
282282
const subFields = subFieldFn({ node, req })
283283
const nodeSiblingData = subFieldDataFn({ node, req }) ?? {}
284-
const nodeSiblingDoc = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
284+
const nodeSiblingDoc = subFieldDataFn({ node: originalNodeIDMap[id]!, req }) ?? {}
285285
const nodePreviousSiblingDoc =
286-
subFieldDataFn({ node: previousNodeIDMap[id], req }) ?? {}
286+
subFieldDataFn({ node: previousNodeIDMap[id]!, req }) ?? {}
287287

288288
if (subFields?.length) {
289289
await afterChangeTraverseFields({
@@ -540,7 +540,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
540540
originalNodeWithLocales: originalNodeWithLocalesIDMap[id],
541541
parentRichTextFieldPath: path,
542542
parentRichTextFieldSchemaPath: schemaPath,
543-
previousNode: previousNodeIDMap[id],
543+
previousNode: previousNodeIDMap[id]!,
544544
req,
545545
skipValidation: skipValidation!,
546546
})
@@ -557,11 +557,11 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
557557
const nodeSiblingData = subFieldDataFn({ node, req }) ?? {}
558558
const nodeSiblingDocWithLocales =
559559
subFieldDataFn({
560-
node: originalNodeWithLocalesIDMap[id],
560+
node: originalNodeWithLocalesIDMap[id]!,
561561
req,
562562
}) ?? {}
563563
const nodePreviousSiblingDoc =
564-
subFieldDataFn({ node: previousNodeIDMap[id], req }) ?? {}
564+
subFieldDataFn({ node: previousNodeIDMap[id]!, req }) ?? {}
565565

566566
if (subFields?.length) {
567567
await beforeChangeTraverseFields({
@@ -756,7 +756,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
756756
if (subFieldFn && subFieldDataFn) {
757757
const subFields = subFieldFn({ node, req })
758758
const nodeSiblingData = subFieldDataFn({ node, req }) ?? {}
759-
const nodeSiblingDoc = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
759+
const nodeSiblingDoc = subFieldDataFn({ node: originalNodeIDMap[id]!, req }) ?? {}
760760

761761
if (subFields?.length) {
762762
await beforeValidateTraverseFields({

packages/richtext-lexical/src/lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/LexicalMenu.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,26 +82,26 @@ function getFullMatchOffset(documentText: string, entryText: string, offset: num
8282
* Split Lexical TextNode and return a new TextNode only containing matched text.
8383
* Common use cases include: removing the node, replacing with a new node.
8484
*/
85-
function $splitNodeContainingQuery(match: MenuTextMatch): null | TextNode {
85+
function $splitNodeContainingQuery(match: MenuTextMatch): TextNode | undefined {
8686
const selection = $getSelection()
8787
if (!$isRangeSelection(selection) || !selection.isCollapsed()) {
88-
return null
88+
return
8989
}
9090
const anchor = selection.anchor
9191
if (anchor.type !== 'text') {
92-
return null
92+
return
9393
}
9494
const anchorNode = anchor.getNode()
9595
if (!anchorNode.isSimpleText()) {
96-
return null
96+
return
9797
}
9898
const selectionOffset = anchor.offset
9999
const textContent = anchorNode.getTextContent().slice(0, selectionOffset)
100100
const characterOffset = match.replaceableString.length
101101
const queryOffset = getFullMatchOffset(textContent, match.matchingString, characterOffset)
102102
const startOffset = selectionOffset - queryOffset
103103
if (startOffset < 0) {
104-
return null
104+
return
105105
}
106106
let newNode
107107
if (startOffset === 0) {
@@ -241,7 +241,7 @@ export function LexicalMenu({
241241
const allItems = groups.flatMap((group) => group.items)
242242

243243
if (allItems.length) {
244-
const firstMatchingItem = allItems[0]
244+
const firstMatchingItem = allItems[0]!
245245
updateSelectedItem(firstMatchingItem)
246246
}
247247
}
@@ -334,6 +334,9 @@ export function LexicalMenu({
334334
const newSelectedIndex = selectedIndex !== allItems.length - 1 ? selectedIndex + 1 : 0
335335

336336
const newSelectedItem = allItems[newSelectedIndex]
337+
if (!newSelectedItem) {
338+
return false
339+
}
337340

338341
updateSelectedItem(newSelectedItem)
339342
if (newSelectedItem.ref != null && newSelectedItem.ref.current) {
@@ -360,6 +363,9 @@ export function LexicalMenu({
360363
const newSelectedIndex = selectedIndex !== 0 ? selectedIndex - 1 : allItems.length - 1
361364

362365
const newSelectedItem = allItems[newSelectedIndex]
366+
if (!newSelectedItem) {
367+
return false
368+
}
363369

364370
updateSelectedItem(newSelectedItem)
365371
if (newSelectedItem.ref != null && newSelectedItem.ref.current) {

packages/richtext-lexical/src/lexical/plugins/SlashMenu/useMenuTriggerMatch.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,18 @@ export function useMenuTriggerMatch(
4747
)
4848
const match = TypeaheadTriggerRegex.exec(query)
4949
if (match !== null) {
50-
const maybeLeadingWhitespace = match[1]
50+
const maybeLeadingWhitespace = match[1]!
5151

5252
/**
5353
* matchingString is only the text AFTER the trigger text. (So everything after the /)
5454
*/
55-
const matchingString = match[3]
55+
const matchingString = match[3]!
5656

5757
if (matchingString.length >= minLength) {
5858
return {
5959
leadOffset: match.index + maybeLeadingWhitespace.length,
6060
matchingString,
61-
replaceableString: match[2], // replaceableString is the trigger text + the matching string
61+
replaceableString: match[2]!, // replaceableString is the trigger text + the matching string
6262
}
6363
}
6464
}

packages/richtext-lexical/src/lexical/plugins/handles/utils/getNodeCloseToPoint.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,8 @@ export function getNodeCloseToPoint(props: Props): Output {
9898
// Return null if matching block element is the first or last node
9999
editor.getEditorState().read(() => {
100100
if (useEdgeAsDefault) {
101-
const [firstNode, lastNode] = [
102-
editor.getElementByKey(topLevelNodeKeys[0]),
103-
editor.getElementByKey(topLevelNodeKeys[topLevelNodeKeys.length - 1]),
104-
]
101+
const firstNode = editor.getElementByKey(topLevelNodeKeys[0]!)
102+
const lastNode = editor.getElementByKey(topLevelNodeKeys[topLevelNodeKeys.length - 1]!)
105103

106104
if (firstNode && lastNode) {
107105
const [firstNodeRect, lastNodeRect] = [
@@ -112,11 +110,11 @@ export function getNodeCloseToPoint(props: Props): Output {
112110
if (y < firstNodeRect.top) {
113111
closestBlockElem.blockElem = firstNode
114112
closestBlockElem.distance = firstNodeRect.top - y
115-
closestBlockElem.blockNode = $getNodeByKey(topLevelNodeKeys[0])
113+
closestBlockElem.blockNode = $getNodeByKey(topLevelNodeKeys[0]!)
116114
closestBlockElem.foundAtIndex = 0
117115
} else if (y > lastNodeRect.bottom) {
118116
closestBlockElem.distance = y - lastNodeRect.bottom
119-
closestBlockElem.blockNode = $getNodeByKey(topLevelNodeKeys[topLevelNodeKeys.length - 1])
117+
closestBlockElem.blockNode = $getNodeByKey(topLevelNodeKeys[topLevelNodeKeys.length - 1]!)
120118
closestBlockElem.blockElem = lastNode
121119
closestBlockElem.foundAtIndex = topLevelNodeKeys.length - 1
122120
}
@@ -135,7 +133,7 @@ export function getNodeCloseToPoint(props: Props): Output {
135133
let direction = Indeterminate
136134

137135
while (index >= 0 && index < topLevelNodeKeys.length) {
138-
const key = topLevelNodeKeys[index]
136+
const key = topLevelNodeKeys[index]!
139137
const elem = editor.getElementByKey(key)
140138
if (elem === null) {
141139
break

0 commit comments

Comments
 (0)