{"record":{"id":"841f63d6eec0fda6","repo":"languagetool-org/languagetool","slug":"plaintextposition-must-be-0-plaintextpositio","errorCode":null,"errorMessage":"plainTextPosition must be >= 0: ${plainTextPosition}","messagePattern":"plainTextPosition must be >= 0: (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"languagetool-core/src/main/java/org/languagetool/markup/AnnotatedText.java","lineNumber":111,"sourceCode":"    StringBuilder sb = new StringBuilder();\n    for (TextPart part : parts) {\n      if (part.getType() != TextPart.Type.FAKE_CONTENT) {\n        sb.append(part.getPart());\n      }\n    }\n    return sb.toString();\n  }\n\n  /**\n   * Internally used by LanguageTool to adjust error positions to point to the\n   * original location with markup, even though markup was ignored during text checking.\n   * @param plainTextPosition the position in the plain text (no markup) that was checked\n   * @param isToPos the from/to position needed\n   * @return an adjusted position of the same location in the text with markup\n   */\n  public int getOriginalTextPositionFor(int plainTextPosition, boolean isToPos) {\n    if (plainTextPosition < 0) {\n      throw new IllegalArgumentException(\"plainTextPosition must be >= 0: \" + plainTextPosition);\n    }\n    if (mapping.isEmpty()) {\n      return 0;\n    }\n    int minDiff = Integer.MAX_VALUE;\n    MappingValue bestMatch = null;\n    // algorithm: find the closest lower position\n    for (Map.Entry<Integer, MappingValue> entry : mapping.entrySet()) {\n      int maybeClosePosition = entry.getKey();\n      if (plainTextPosition < maybeClosePosition) {\n        int diff = maybeClosePosition - plainTextPosition;\n        if (diff > 0 && diff < minDiff) {\n          bestMatch = entry.getValue();\n          minDiff = diff;\n        }\n      }\n    }\n    if (bestMatch == null) {","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-core/src/main/java/org/languagetool/markup/AnnotatedText.java#L93-L129","documentation":"AnnotatedText.getOriginalTextPositionFor() maps a position in the plain text (markup stripped) back to the original document position. Negative positions are meaningless, so it throws immediately. This is an input-validation error in the caller's offset arithmetic.","triggerScenarios":"Calling getOriginalTextPositionFor() with a negative plainTextPosition, typically from rule-match offset adjustment (adjustRuleMatchPos/adjustOffset/getRangesFromSentences) when a rule produced an erroneous negative offset (e.g. regex lookarounds or matching the empty string at position 0 shifting offsets).","commonSituations":"A regex rule matching an empty string or using lookbehind/lookahead that yields match start < 0 in the plain-text view; bugs in custom rules' offset calculations.","solutions":["Fix the rule producing the negative offset (empty match or lookaround shifting start before position 0).","Clamp offsets: Math.max(0, plainTextPosition) before calling the mapping API.","Validate that fromPos/toPos of the rule match are >= 0 before adjusting positions.","Update/upgrade the rule definition if it comes from a shipped grammar/wording rule set."],"exampleFix":"// before\nint orig = text.getOriginalTextPositionFor(match.getOffset(), false);\n// after\nint plainPos = Math.max(0, match.getOffset());\nint orig = text.getOriginalTextPositionFor(plainPos, false);","handlingStrategy":"validation","validationCode":"if (plainTextPosition < 0) plainTextPosition = 0; // or reject the rule match\nint orig = text.getOriginalTextPositionFor(plainTextPosition, isToPos);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Clamp rule-match offsets to >= 0 before mapping","Fix rules whose regex can match empty strings or shift starts before position 0","Assert match.getFromPos() >= 0 in custom rule code"],"tags":["offset-mapping","annotation","argument-validation"],"backgroundTag":"argument-out-of-range","analyzedSha":"2e990059ce67d5e2a0f7f7ca5d31160c6709df4b","analyzedAt":"2026-09-06T09:20:17.015Z","contentChangedAt":"2026-09-06T09:20:17.015Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}