std::string TextDiagnostic::buildFixItInsertionLine( unsigned LineNo, const SourceColumnMap &map, ArrayRef<FixItHint> Hints) { std::string FixItInsertionLine; if (Hints.empty() || !DiagOpts.ShowFixits) return FixItInsertionLine; for (ArrayRef<FixItHint>::iterator I = Hints.begin(), E = Hints.end(); I != E; ++I) { if (!I->CodeToInsert.empty()) { // We have an insertion hint. Determine whether the inserted // code is on the same line as the caret. std::pair<FileID, unsigned> HintLocInfo = SM.getDecomposedExpansionLoc(I->RemoveRange.getBegin()); if (LineNo == SM.getLineNumber(HintLocInfo.first, HintLocInfo.second)) { // Insert the new code into the line just below the code // that the user wrote. unsigned HintColNo = SM.getColumnNumber(HintLocInfo.first, HintLocInfo.second) - 1; // hint must start inside the source or right at the end assert(HintColNo<static_cast<unsigned>(map.bytes())+1); HintColNo = map.byteToColumn(HintColNo); // FIXME: if the fixit includes tabs or other characters that do not // take up a single column per byte when displayed then // I->CodeToInsert.size() is not a column number and we're mixing // units (columns + bytes). We should get printable versions // of each fixit before using them. unsigned LastColumnModified = HintColNo + I->CodeToInsert.size(); if (LastColumnModified > static_cast<unsigned>(map.bytes())) { unsigned LastExistingColumn = map.byteToColumn(map.bytes()); unsigned AddedColumns = LastColumnModified-LastExistingColumn; LastColumnModified = LastExistingColumn + AddedColumns; } else { LastColumnModified = map.byteToColumn(LastColumnModified); } if (LastColumnModified > FixItInsertionLine.size()) FixItInsertionLine.resize(LastColumnModified, ' '); assert(HintColNo+I->CodeToInsert.size() <= FixItInsertionLine.size()); std::copy(I->CodeToInsert.begin(), I->CodeToInsert.end(), FixItInsertionLine.begin() + HintColNo); } else { FixItInsertionLine.clear(); break; } } } expandTabs(FixItInsertionLine, DiagOpts.TabStop); return FixItInsertionLine; }
/// \brief When the source code line we want to print is too long for /// the terminal, select the "interesting" region. static void selectInterestingSourceRegion(std::string &SourceLine, std::string &CaretLine, std::string &FixItInsertionLine, unsigned Columns, const SourceColumnMap &map) { unsigned MaxColumns = std::max<unsigned>(map.columns(), std::max(CaretLine.size(), FixItInsertionLine.size())); // if the number of columns is less than the desired number we're done if (MaxColumns <= Columns) return; // no special characters allowed in CaretLine or FixItInsertionLine assert(CaretLine.end() == std::find_if(CaretLine.begin(), CaretLine.end(), char_out_of_range(' ','~'))); assert(FixItInsertionLine.end() == std::find_if(FixItInsertionLine.begin(), FixItInsertionLine.end(), char_out_of_range(' ','~'))); // Find the slice that we need to display the full caret line // correctly. unsigned CaretStart = 0, CaretEnd = CaretLine.size(); for (; CaretStart != CaretEnd; ++CaretStart) if (!isspace(static_cast<unsigned char>(CaretLine[CaretStart]))) break; for (; CaretEnd != CaretStart; --CaretEnd) if (!isspace(static_cast<unsigned char>(CaretLine[CaretEnd - 1]))) break; // caret has already been inserted into CaretLine so the above whitespace // check is guaranteed to include the caret // If we have a fix-it line, make sure the slice includes all of the // fix-it information. if (!FixItInsertionLine.empty()) { unsigned FixItStart = 0, FixItEnd = FixItInsertionLine.size(); for (; FixItStart != FixItEnd; ++FixItStart) if (!isspace(static_cast<unsigned char>(FixItInsertionLine[FixItStart]))) break; for (; FixItEnd != FixItStart; --FixItEnd) if (!isspace(static_cast<unsigned char>(FixItInsertionLine[FixItEnd - 1]))) break; CaretStart = std::min(FixItStart, CaretStart); CaretEnd = std::max(FixItEnd, CaretEnd); } // CaretEnd may have been set at the middle of a character // If it's not at a character's first column then advance it past the current // character. while (static_cast<int>(CaretEnd) < map.columns() && -1 == map.columnToByte(CaretEnd)) ++CaretEnd; assert((static_cast<int>(CaretStart) > map.columns() || -1!=map.columnToByte(CaretStart)) && "CaretStart must not point to a column in the middle of a source" " line character"); assert((static_cast<int>(CaretEnd) > map.columns() || -1!=map.columnToByte(CaretEnd)) && "CaretEnd must not point to a column in the middle of a source line" " character"); // CaretLine[CaretStart, CaretEnd) contains all of the interesting // parts of the caret line. While this slice is smaller than the // number of columns we have, try to grow the slice to encompass // more context. unsigned SourceStart = map.columnToByte(std::min<unsigned>(CaretStart, map.columns())); unsigned SourceEnd = map.columnToByte(std::min<unsigned>(CaretEnd, map.columns())); unsigned CaretColumnsOutsideSource = CaretEnd-CaretStart - (map.byteToColumn(SourceEnd)-map.byteToColumn(SourceStart)); char const *front_ellipse = " ..."; char const *front_space = " "; char const *back_ellipse = "..."; unsigned ellipses_space = strlen(front_ellipse) + strlen(back_ellipse); unsigned TargetColumns = Columns; // Give us extra room for the ellipses // and any of the caret line that extends past the source if (TargetColumns > ellipses_space+CaretColumnsOutsideSource) TargetColumns -= ellipses_space+CaretColumnsOutsideSource; while (SourceStart>0 || SourceEnd<SourceLine.size()) { bool ExpandedRegion = false; if (SourceStart>0) { unsigned NewStart = map.startOfPreviousColumn(SourceStart); // Skip over any whitespace we see here; we're looking for // another bit of interesting text. // FIXME: Detect non-ASCII whitespace characters too. while (NewStart && isspace(static_cast<unsigned char>(SourceLine[NewStart]))) NewStart = map.startOfPreviousColumn(NewStart); // Skip over this bit of "interesting" text. while (NewStart) { unsigned Prev = map.startOfPreviousColumn(NewStart); if (isspace(static_cast<unsigned char>(SourceLine[Prev]))) break; NewStart = Prev; } assert(map.byteToColumn(NewStart) != -1); unsigned NewColumns = map.byteToColumn(SourceEnd) - map.byteToColumn(NewStart); if (NewColumns <= TargetColumns) { SourceStart = NewStart; ExpandedRegion = true; } } if (SourceEnd<SourceLine.size()) { unsigned NewEnd = map.startOfNextColumn(SourceEnd); // Skip over any whitespace we see here; we're looking for // another bit of interesting text. // FIXME: Detect non-ASCII whitespace characters too. while (NewEnd < SourceLine.size() && isspace(static_cast<unsigned char>(SourceLine[NewEnd]))) NewEnd = map.startOfNextColumn(NewEnd); // Skip over this bit of "interesting" text. while (NewEnd < SourceLine.size() && !isspace(static_cast<unsigned char>(SourceLine[NewEnd]))) NewEnd = map.startOfNextColumn(NewEnd); assert(map.byteToColumn(NewEnd) != -1); unsigned NewColumns = map.byteToColumn(NewEnd) - map.byteToColumn(SourceStart); if (NewColumns <= TargetColumns) { SourceEnd = NewEnd; ExpandedRegion = true; } } if (!ExpandedRegion) break; } CaretStart = map.byteToColumn(SourceStart); CaretEnd = map.byteToColumn(SourceEnd) + CaretColumnsOutsideSource; // [CaretStart, CaretEnd) is the slice we want. Update the various // output lines to show only this slice, with two-space padding // before the lines so that it looks nicer. assert(CaretStart!=(unsigned)-1 && CaretEnd!=(unsigned)-1 && SourceStart!=(unsigned)-1 && SourceEnd!=(unsigned)-1); assert(SourceStart <= SourceEnd); assert(CaretStart <= CaretEnd); unsigned BackColumnsRemoved = map.byteToColumn(SourceLine.size())-map.byteToColumn(SourceEnd); unsigned FrontColumnsRemoved = CaretStart; unsigned ColumnsKept = CaretEnd-CaretStart; // We checked up front that the line needed truncation assert(FrontColumnsRemoved+ColumnsKept+BackColumnsRemoved > Columns); // The line needs some trunctiona, and we'd prefer to keep the front // if possible, so remove the back if (BackColumnsRemoved > strlen(back_ellipse)) SourceLine.replace(SourceEnd, std::string::npos, back_ellipse); // If that's enough then we're done if (FrontColumnsRemoved+ColumnsKept <= Columns) return; // Otherwise remove the front as well if (FrontColumnsRemoved > strlen(front_ellipse)) { SourceLine.replace(0, SourceStart, front_ellipse); CaretLine.replace(0, CaretStart, front_space); if (!FixItInsertionLine.empty()) FixItInsertionLine.replace(0, CaretStart, front_space); } }
/// \brief Highlight a SourceRange (with ~'s) for any characters on LineNo. void TextDiagnostic::highlightRange(const CharSourceRange &R, unsigned LineNo, FileID FID, const SourceColumnMap &map, std::string &CaretLine) { if (!R.isValid()) return; SourceLocation Begin = SM.getExpansionLoc(R.getBegin()); SourceLocation End = SM.getExpansionLoc(R.getEnd()); // If the End location and the start location are the same and are a macro // location, then the range was something that came from a macro expansion // or _Pragma. If this is an object-like macro, the best we can do is to // highlight the range. If this is a function-like macro, we'd also like to // highlight the arguments. if (Begin == End && R.getEnd().isMacroID()) End = SM.getExpansionRange(R.getEnd()).second; unsigned StartLineNo = SM.getExpansionLineNumber(Begin); if (StartLineNo > LineNo || SM.getFileID(Begin) != FID) return; // No intersection. unsigned EndLineNo = SM.getExpansionLineNumber(End); if (EndLineNo < LineNo || SM.getFileID(End) != FID) return; // No intersection. // Compute the column number of the start. unsigned StartColNo = 0; if (StartLineNo == LineNo) { StartColNo = SM.getExpansionColumnNumber(Begin); if (StartColNo) --StartColNo; // Zero base the col #. } // Compute the column number of the end. unsigned EndColNo = map.getSourceLine().size(); if (EndLineNo == LineNo) { EndColNo = SM.getExpansionColumnNumber(End); if (EndColNo) { --EndColNo; // Zero base the col #. // Add in the length of the token, so that we cover multi-char tokens if // this is a token range. if (R.isTokenRange()) EndColNo += Lexer::MeasureTokenLength(End, SM, LangOpts); } else { EndColNo = CaretLine.size(); } } assert(StartColNo <= EndColNo && "Invalid range!"); // Check that a token range does not highlight only whitespace. if (R.isTokenRange()) { // Pick the first non-whitespace column. while (StartColNo < map.getSourceLine().size() && (map.getSourceLine()[StartColNo] == ' ' || map.getSourceLine()[StartColNo] == '\t')) ++StartColNo; // Pick the last non-whitespace column. if (EndColNo > map.getSourceLine().size()) EndColNo = map.getSourceLine().size(); while (EndColNo-1 && (map.getSourceLine()[EndColNo-1] == ' ' || map.getSourceLine()[EndColNo-1] == '\t')) --EndColNo; // If the start/end passed each other, then we are trying to highlight a // range that just exists in whitespace, which must be some sort of other // bug. assert(StartColNo <= EndColNo && "Trying to highlight whitespace??"); } assert(StartColNo <= map.getSourceLine().size() && "Invalid range!"); assert(EndColNo <= map.getSourceLine().size() && "Invalid range!"); // Fill the range with ~'s. StartColNo = map.byteToColumn(StartColNo); EndColNo = map.byteToColumn(EndColNo); assert(StartColNo <= EndColNo && "Invalid range!"); if (CaretLine.size() < EndColNo) CaretLine.resize(EndColNo,' '); std::fill(CaretLine.begin()+StartColNo,CaretLine.begin()+EndColNo,'~'); }
/// \brief Emit a code snippet and caret line. /// /// This routine emits a single line's code snippet and caret line.. /// /// \param Loc The location for the caret. /// \param Ranges The underlined ranges for this code snippet. /// \param Hints The FixIt hints active for this diagnostic. void TextDiagnostic::emitSnippetAndCaret( SourceLocation Loc, DiagnosticsEngine::Level Level, SmallVectorImpl<CharSourceRange>& Ranges, ArrayRef<FixItHint> Hints) { assert(!Loc.isInvalid() && "must have a valid source location here"); assert(Loc.isFileID() && "must have a file location here"); // If caret diagnostics are enabled and we have location, we want to // emit the caret. However, we only do this if the location moved // from the last diagnostic, if the last diagnostic was a note that // was part of a different warning or error diagnostic, or if the // diagnostic has ranges. We don't want to emit the same caret // multiple times if one loc has multiple diagnostics. if (!DiagOpts.ShowCarets) return; if (Loc == LastLoc && Ranges.empty() && Hints.empty() && (LastLevel != DiagnosticsEngine::Note || Level == LastLevel)) return; // Decompose the location into a FID/Offset pair. std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc); FileID FID = LocInfo.first; unsigned FileOffset = LocInfo.second; // Get information about the buffer it points into. bool Invalid = false; const char *BufStart = SM.getBufferData(FID, &Invalid).data(); if (Invalid) return; unsigned LineNo = SM.getLineNumber(FID, FileOffset); unsigned ColNo = SM.getColumnNumber(FID, FileOffset); unsigned CaretEndColNo = ColNo + Lexer::MeasureTokenLength(Loc, SM, LangOpts); // Rewind from the current position to the start of the line. const char *TokPtr = BufStart+FileOffset; const char *LineStart = TokPtr-ColNo+1; // Column # is 1-based. // Compute the line end. Scan forward from the error position to the end of // the line. const char *LineEnd = TokPtr; while (*LineEnd != '\n' && *LineEnd != '\r' && *LineEnd != '\0') ++LineEnd; // FIXME: This shouldn't be necessary, but the CaretEndColNo can extend past // the source line length as currently being computed. See // test/Misc/message-length.c. CaretEndColNo = std::min(CaretEndColNo, unsigned(LineEnd - LineStart)); // Copy the line of code into an std::string for ease of manipulation. std::string SourceLine(LineStart, LineEnd); // Create a line for the caret that is filled with spaces that is the same // length as the line of source code. std::string CaretLine(LineEnd-LineStart, ' '); const SourceColumnMap sourceColMap(SourceLine, DiagOpts.TabStop); // Highlight all of the characters covered by Ranges with ~ characters. for (SmallVectorImpl<CharSourceRange>::iterator I = Ranges.begin(), E = Ranges.end(); I != E; ++I) highlightRange(*I, LineNo, FID, sourceColMap, CaretLine); // Next, insert the caret itself. ColNo = sourceColMap.byteToColumn(ColNo-1); if (CaretLine.size()<ColNo+1) CaretLine.resize(ColNo+1, ' '); CaretLine[ColNo] = '^'; std::string FixItInsertionLine = buildFixItInsertionLine(LineNo, sourceColMap, Hints); // If the source line is too long for our terminal, select only the // "interesting" source region within that line. unsigned Columns = DiagOpts.MessageLength; if (Columns) selectInterestingSourceRegion(SourceLine, CaretLine, FixItInsertionLine, Columns, sourceColMap); // If we are in -fdiagnostics-print-source-range-info mode, we are trying // to produce easily machine parsable output. Add a space before the // source line and the caret to make it trivial to tell the main diagnostic // line from what the user is intended to see. if (DiagOpts.ShowSourceRanges) { SourceLine = ' ' + SourceLine; CaretLine = ' ' + CaretLine; } // Finally, remove any blank spaces from the end of CaretLine. while (CaretLine[CaretLine.size()-1] == ' ') CaretLine.erase(CaretLine.end()-1); // Emit what we have computed. emitSnippet(SourceLine); if (DiagOpts.ShowColors) OS.changeColor(caretColor, true); OS << CaretLine << '\n'; if (DiagOpts.ShowColors) OS.resetColor(); if (!FixItInsertionLine.empty()) { if (DiagOpts.ShowColors) // Print fixit line in color OS.changeColor(fixitColor, false); if (DiagOpts.ShowSourceRanges) OS << ' '; OS << FixItInsertionLine << '\n'; if (DiagOpts.ShowColors) OS.resetColor(); } // Print out any parseable fixit information requested by the options. emitParseableFixits(Hints); }
std::string TextDiagnostic::buildFixItInsertionLine( unsigned LineNo, const SourceColumnMap &map, ArrayRef<FixItHint> Hints, const SourceManager &SM) { std::string FixItInsertionLine; if (Hints.empty() || !DiagOpts.ShowFixits) return FixItInsertionLine; unsigned PrevHintEndCol = 0; for (ArrayRef<FixItHint>::iterator I = Hints.begin(), E = Hints.end(); I != E; ++I) { if (!I->CodeToInsert.empty()) { // We have an insertion hint. Determine whether the inserted // code contains no newlines and is on the same line as the caret. std::pair<FileID, unsigned> HintLocInfo = SM.getDecomposedExpansionLoc(I->RemoveRange.getBegin()); if (LineNo == SM.getLineNumber(HintLocInfo.first, HintLocInfo.second) && StringRef(I->CodeToInsert).find_first_of("\n\r") == StringRef::npos) { // Insert the new code into the line just below the code // that the user wrote. // Note: When modifying this function, be very careful about what is a // "column" (printed width, platform-dependent) and what is a // "byte offset" (SourceManager "column"). unsigned HintByteOffset = SM.getColumnNumber(HintLocInfo.first, HintLocInfo.second) - 1; // The hint must start inside the source or right at the end assert(HintByteOffset < static_cast<unsigned>(map.bytes())+1); unsigned HintCol = map.byteToColumn(HintByteOffset); // If we inserted a long previous hint, push this one forwards, and add // an extra space to show that this is not part of the previous // completion. This is sort of the best we can do when two hints appear // to overlap. // // Note that if this hint is located immediately after the previous // hint, no space will be added, since the location is more important. if (HintCol < PrevHintEndCol) HintCol = PrevHintEndCol + 1; // FIXME: This function handles multibyte characters in the source, but // not in the fixits. This assertion is intended to catch unintended // use of multibyte characters in fixits. If we decide to do this, we'll // have to track separate byte widths for the source and fixit lines. assert((size_t)llvm::sys::locale::columnWidth(I->CodeToInsert) == I->CodeToInsert.size()); // This relies on one byte per column in our fixit hints. // This should NOT use HintByteOffset, because the source might have // Unicode characters in earlier columns. unsigned LastColumnModified = HintCol + I->CodeToInsert.size(); if (LastColumnModified > FixItInsertionLine.size()) FixItInsertionLine.resize(LastColumnModified, ' '); std::copy(I->CodeToInsert.begin(), I->CodeToInsert.end(), FixItInsertionLine.begin() + HintCol); PrevHintEndCol = LastColumnModified; } else { FixItInsertionLine.clear(); break; } } } expandTabs(FixItInsertionLine, DiagOpts.TabStop); return FixItInsertionLine; }