Repository: nvaccess/nvda

Issue:
Warning appeared in NVDA 2021.3beta2
I report here a warning that has appeared at various places in 2021.3beta2 (not tested the beta1) but was not present in 2021.2.
Maybe introduced by #12925? (Cc @michaelDCurran )

Also Cc @feerrenrut to decide if this issue should be in the 2021.3 milestone or not.

### Steps to reproduce:

* Open a setting dialog, e.g. NVDA+control+D (formatting)
* Press space key to modify the state of the first checkbox.

### Actual behavior:

The following warning appears in the log
```
DEBUGWARNING - NVDAObjects.NVDAObject._reportErrorInPreviousWord (21:08:34.573) - MainThread (5336):
Error fetching last character of previous word
Traceback (most recent call last):
  File "NVDAObjects\__init__.pyc", line 1050, in _reportErrorInPreviousWord
  File "documentBase.pyc", line 22, in makeTextInfo
  File "textInfos\offsets.pyc", line 470, in __init__
  File "textInfos\offsets.pyc", line 249, in _getCaretOffset
NotImplementedError
```

I have not noted however any other negative side effect.

### Expected behavior:

No such warning that makes the log more difficult to read and that does not bring any useful information in this case. IMO, such a warning should not be present for objects that do not have text edition capability.
Why is the function `_reportErrorInPreviousWord` present and called for a such generic object?

If I have misunderstood the goal of this warning however, please explain and close this issue.

### System configuration
#### NVDA installed/portable/running from source:
Installed
#### NVDA version:
2021.3beta2

#### Windows version:
Windows 10 20H2 (64-bit) build 19042.1288

#### Name and version of other software in use when reproducing the issue:
N/A
#### Other information about your system:
N/A
### Other questions
#### Does the issue still occur after restarting your computer?
Yes
#### Have you tried any other versions of NVDA? If so, please report their behaviors.
2021.2: no warning
#### If NVDA add-ons are disabled, is your problem still occurring?
Yes
#### Does the issue still occur after you run the COM Registration Fixing Tool in NVDA's tools menu?
Not tested but probably not related.



Discussion:
We need to understand the severity of this before we can determine if it needs to be fixed for 2021.3, further investigation is required:
- Which PR was this introduced with?
- Why is this code being called in this situation?
- Does the exception prevent something useful from happening?

@CyrilleB79 these aren't necessarily questions for you, but you are welcome to investigate and try to answer them if you wish.
these warnings were introduced in pr #12925  / pr #12931 .
They are harmless and the exceptions they talk about are expected.
We could silence these particular warnings, I.e. for NotImplemented, but I certainly wouldn't bother for NVDA 2021.3.
Thanks for looking @michaelDCurran. I'm going to bump this to fix in 2022.1

"Expect-tions" don't really do anyone any favors. 
I just confirm that I have not seen any undesirable side effect.

What I do not fully  understand is why the code for spelling errors detection is in the base  NVDAObject as opposed to being in `editableText.EditableText`. Are there cases in which spelling error can be detected when typing outside of editable content?

Resolving patch:
diff --git a/source/NVDAObjects/__init__.py b/source/NVDAObjects/__init__.py
--- a/source/NVDAObjects/__init__.py
+++ b/source/NVDAObjects/__init__.py
@@ -8,12 +8,9 @@
 """Module that contains the base NVDA object type with dynamic class creation support,
 as well as the associated TextInfo class."""
 
-import os
 import time
-import re
 import typing
 import weakref
-import core
 import textUtils
 from logHandler import log
 import review
@@ -35,7 +32,6 @@
 import brailleInput
 import locationHelper
 import aria
-import globalVars
 
 
 class NVDAObjectTextInfo(textInfos.offsets.OffsetsTextInfo):
@@ -1059,45 +1055,6 @@ def _get_landmark(self):
 		"""
 		return None
 
-	def _reportErrorInPreviousWord(self):
-		try:
-			# self might be a descendant of the text control; e.g. Symphony.
-			# We want to deal with the entire text, so use the caret object.
-			info = api.getCaretObject().makeTextInfo(textInfos.POSITION_CARET)
-			# This gets called for characters which might end a word; e.g. space.
-			# The character before the caret is the word end.
-			# The one before that is the last of the word, which is what we want.
-			info.move(textInfos.UNIT_CHARACTER, -2)
-			info.expand(textInfos.UNIT_CHARACTER)
-		except Exception:
-			# Focus probably moved.
-			log.debugWarning("Error fetching last character of previous word", exc_info=True)
-			return
-
-		# Fetch the formatting for the last word to see if it is marked as a spelling error,
-		# However perform the fetch and check in a future core cycle
-		# To give the content control more time to detect and mark the error itself.
-		# #12161: MS Word's UIA implementation certainly requires this delay.
-		def _delayedDetection():
-			try:
-				fields = info.getTextWithFields()
-			except Exception:
-				log.debugWarning("Error fetching formatting for last character of previous word", exc_info=True)
-				return
-			for command in fields:
-				if (
-					isinstance(command, textInfos.FieldCommand)
-					and command.command == "formatChange"
-					and command.field.get("invalid-spelling")
-				):
-					break
-			else:
-				# No error.
-				return
-			import nvwave
-			nvwave.playWaveFile(os.path.join(globalVars.appDir, "waves", "textError.wav"))
-		core.callLater(50, _delayedDetection)
-
 	def _get_liveRegionPoliteness(self) -> aria.AriaLivePoliteness:
 		""" Retrieves the priority with which  updates to live regions should be treated.
 		The base implementation returns C{aria.AriaLivePoliteness.OFF},
@@ -1125,12 +1082,6 @@ def event_liveRegionChange(self):
 			)
 
 	def event_typedCharacter(self,ch):
-		if config.conf["documentFormatting"]["reportSpellingErrors"] and config.conf["keyboard"]["alertForSpellingErrors"] and (
-			# Not alpha, apostrophe or control.
-			ch.isspace() or (ch >= u" " and ch not in u"'\x7f" and not ch.isalpha())
-		):
-			# Reporting of spelling errors is enabled and this character ends a word.
-			self._reportErrorInPreviousWord()
 		speech.speakTypedCharacters(ch)
 		import winUser
 		if config.conf["keyboard"]["beepForLowercaseWithCapslock"] and ch.islower() and winUser.getKeyState(winUser.VK_CAPITAL)&1:
diff --git a/source/NVDAObjects/behaviors.py b/source/NVDAObjects/behaviors.py
--- a/source/NVDAObjects/behaviors.py
+++ b/source/NVDAObjects/behaviors.py
@@ -26,6 +26,7 @@
 import api
 import ui
 import braille
+import core
 import nvwave
 import globalVars
 from typing import List, Union
@@ -177,6 +178,58 @@ def _caretScriptPostMovedHelper(self, speakUnit, gesture, info=None):
 			return
 		super()._caretScriptPostMovedHelper(speakUnit, gesture, info)
 
+	def _reportErrorInPreviousWord(self):
+		try:
+			# self might be a descendant of the text control; e.g. Symphony.
+			# We want to deal with the entire text, so use the caret object.
+			info = api.getCaretObject().makeTextInfo(textInfos.POSITION_CARET)
+			# This gets called for characters which might end a word; e.g. space.
+			# The character before the caret is the word end.
+			# The one before that is the last of the word, which is what we want.
+			info.move(textInfos.UNIT_CHARACTER, -2)
+			info.expand(textInfos.UNIT_CHARACTER)
+		except Exception:
+			# Focus probably moved.
+			log.debugWarning("Error fetching last character of previous word", exc_info=True)
+			return
+
+		# Fetch the formatting for the last word to see if it is marked as a spelling error,
+		# However perform the fetch and check in a future core cycle
+		# To give the content control more time to detect and mark the error itself.
+		# #12161: MS Word's UIA implementation certainly requires this delay.
+		def _delayedDetection():
+			try:
+				fields = info.getTextWithFields()
+			except Exception:
+				log.debugWarning("Error fetching formatting for last character of previous word", exc_info=True)
+				return
+			for command in fields:
+				if (
+					isinstance(command, textInfos.FieldCommand)
+					and command.command == "formatChange"
+					and command.field.get("invalid-spelling")
+				):
+					break
+			else:
+				# No error.
+				return
+			nvwave.playWaveFile(os.path.join(globalVars.appDir, "waves", "textError.wav"))
+		core.callLater(50, _delayedDetection)
+
+	def event_typedCharacter(self, ch: str):
+		if(
+			config.conf["documentFormatting"]["reportSpellingErrors"]
+			and config.conf["keyboard"]["alertForSpellingErrors"]
+			and (
+				# Not alpha, apostrophe or control.
+				ch.isspace() or (ch >= " " and ch not in "'\x7f" and not ch.isalpha())
+			)
+		):
+			# Reporting of spelling errors is enabled and this character ends a word.
+			self._reportErrorInPreviousWord()
+		super().event_typedCharacter(ch)
+
+
 class EditableTextWithAutoSelectDetection(EditableText):
 	"""In addition to L{EditableText}, handles reporting of selection changes for objects which notify of them.
 	To have selection changes reported, the object must notify of selection changes via the caret event.


Test patch: