def _createAddResult(self, results, datalines, errorType, lineNo, desc, numDots): # build lint result object result = KoLintResult() if lineNo >= len(datalines): lineNo = len(datalines) - 1 # if the error is on the last line, work back to the last # character of the first nonblank line so we can display # the error somewhere while lineNo >= 0 and not datalines[lineNo]: lineNo -= 1 if lineNo < 0: return result.columnEnd = len(datalines[lineNo - 1]) + 1 result.columnStart = 1 lineNo += 1 else: if numDots is None: result.columnStart = 1 else: result.columnStart = numDots + 1 result.columnEnd = result.columnStart + 1 result.lineStart = lineNo result.lineEnd = lineNo if (errorType.lower().find('warning') >= 0): result.severity = result.SEV_WARNING else: result.severity = result.SEV_ERROR # This always results in a lint result spanning a single # character, which, given the squiggly reporting scheme is # almost invisible. Workaround: set the result to be the # whole line and append the column number to the description. result.description = "%s: %s (on column %d)" % (errorType,desc,result.columnStart) result.columnStart = 1 result.columnEnd = len(datalines[lineNo-1])+1 results.addResult(result)
def _buildResult(self, resultDict, leadingWS): """Convert a pycompile.py output resultDict to a KoILintResult. A pycompile.py dict looks like this: {'description': 'SyntaxError: invalid syntax', 'filename': 'foo.py', 'lineno': 1, 'offset': 8, # may be None 'severity': 'ERROR', 'text': 'asdf = \n'} """ r = KoLintResult() r.description = resultDict["description"] if leadingWS: resultDict['lineno'] -= 1 if resultDict['text'].startswith(leadingWS): resultDict['text'] = resultDict['text'][len(leadingWS):] if resultDict["offset"] is not None: if leadingWS: actualLineOffset = len(leadingWS) if resultDict['offset'] >= actualLineOffset: resultDict['offset'] -= actualLineOffset r.description += " (at column %d)" % resultDict["offset"] r.lineStart = resultDict['lineno'] r.lineEnd = resultDict['lineno'] # Would be nice to actually underline from teh given offset, but # then have to be smart abouve how far after that to underline. r.columnStart = 1 r.columnEnd = len(resultDict['text']) if resultDict['severity'] == "ERROR": r.severity = r.SEV_ERROR elif resultDict['severity'] == "WARNING": r.severity = r.SEV_WARNING return r
def lint_with_text(self, request, text): if not text.strip(): return None cwd = request.cwd env = koprocessutils.getUserEnv() settingsDir = env.get("DJANGO_SETTINGS_MODULE", None) if not settingsDir: settingsDir = self._getSettingsDir(cwd) if settingsDir: # Save the current buffer to a temporary file. tmpFileName = tempfile.mktemp() fout = open(tmpFileName, 'wb') try: fout.write(text) fout.close() #XXX: How to tell whether we're using Python or Python3? prefName = "pythonExtraPaths" pythonPath = request.prefset.getString(prefName, "") pythonPathEnv = env.get("PYTHONPATH", "") if pythonPathEnv: if pythonPath: pythonPath += os.pathsep + pythonPathEnv else: pythonPath = pythonPathEnv if pythonPath: if sys.platform.startswith("win"): pythonPath = pythonPath.replace('\\', '/') env["PYTHONPATH"] = pythonPath elif env.has_key("PYTHONPATH"): del env["PYTHONPATH"] results = koLintResults() pythonExe = self._pythonInfo.getExecutableFromDocument(request.koDoc) argv = [pythonExe, self._djangoLinterPath, tmpFileName, settingsDir] p = process.ProcessOpen(argv, cwd=cwd, env=env, stdin=None) output, error = p.communicate() #log.debug("Django output: output:[%s], error:[%s]", output, error) retval = p.returncode finally: os.unlink(tmpFileName) if retval == 1: if error: results.addResult(self._buildResult(text, error)) else: results.addResult(self._buildResult(text, "Unexpected error")) else: result = KoLintResult() result.lineStart = 1 result.lineEnd = 1 result.columnStart = 1 result.columnEnd = 1 + len(text.splitlines(1)[0]) result.description = "Can't find settings.py for this Django file" result.encodedDescription = result.description result.severity = result.SEV_ERROR results = koLintResults() results.addResult(result) return results
def _buildResult(self, text, message): inputLines = text.splitlines() r = KoLintResult() r.severity = r.SEV_ERROR r.description = message m = re.compile(r'TemplateSyntaxError:\s*(.*)\n').match(message) if m: message = m.group(1) for errorType in self._simple_matchers: if errorType in message: if self._do_simple_matcher( self._simple_matchers[errorType], text, r): return r for raw_ptn in self._contextual_matchers: if not self._compiled_ptns.has_key(raw_ptn): self._compiled_ptns[raw_ptn] = re.compile(raw_ptn) ptn = self._compiled_ptns[raw_ptn] m = ptn.search(message) if m: if self._do_contextual_matcher( self._contextual_matchers[raw_ptn], m.group(1), text, r): return r if self._test_for_duplicate_extends(message, text, r): return r # Special-case contextual pattern has two parts m = re.compile( r"Could not parse the remainder: '(.*?)' from '(.*?)'").search( message) if m: part2 = m.group(1) part1 = m.group(2)[:-1 * len(part2)] ptn = r'%s(%%s)' % (re.escape(part1), ) res = self._do_contextual_matcher(ptn, part2, text, r) if res: return r # Let the Unclosed Block Tag message fall through, and highlight # last line, since we don't know which open-tag it's referring to # Underline the last line for all other unrecognized syntax errors. r.columnStart = 1 problemLineIdx = len(inputLines) - 1 while problemLineIdx >= 0 and not inputLines[problemLineIdx].strip(): problemLineIdx -= 1 if problemLineIdx == -1: r.lineStart = r.lineEnd = 1 r.columnEnd = 1 else: r.lineStart = r.lineEnd = problemLineIdx + 1 r.columnEnd = len(inputLines[problemLineIdx]) return r
def _buildResult(self, text, message): inputLines = text.splitlines() r = KoLintResult() r.severity = r.SEV_ERROR r.description = message m = re.compile(r'TemplateSyntaxError:\s*(.*)\n').match(message) if m: message = m.group(1) for errorType in self._simple_matchers: if errorType in message: if self._do_simple_matcher( self._simple_matchers[errorType], text, r): return r for raw_ptn in self._contextual_matchers: if raw_ptn not in self._compiled_ptns: self._compiled_ptns[raw_ptn] = re.compile(raw_ptn) ptn = self._compiled_ptns[raw_ptn] m = ptn.search(message) if m: if self._do_contextual_matcher(self._contextual_matchers[raw_ptn], m.group(1), text, r): return r if self._test_for_duplicate_extends(message, text, r): return r # Special-case contextual pattern has two parts m = re.compile( r"Could not parse the remainder: '(.*?)' from '(.*?)'").search(message) if m: part2 = m.group(1) part1 = m.group(2)[:-1 * len(part2)] ptn = r'%s(%%s)' % (re.escape(part1),) res = self._do_contextual_matcher(ptn, part2, text, r) if res: return r # Let the Unclosed Block Tag message fall through, and highlight # last line, since we don't know which open-tag it's referring to # Underline the last line for all other unrecognized syntax errors. r.columnStart = 1 problemLineIdx = len(inputLines) - 1 while problemLineIdx >= 0 and not inputLines[problemLineIdx].strip(): problemLineIdx -= 1 if problemLineIdx == -1: r.lineStart = r.lineEnd = 1 r.columnEnd = 1 else: r.lineStart = r.lineEnd = problemLineIdx + 1 r.columnEnd = len(inputLines[problemLineIdx]) return r
def _buildResult(self, text, message, tempfile_name=None): r = KoLintResult() r.severity = r.SEV_ERROR r.description = message m = re.match('%s:(\d+): (.*)' % tempfile_name or '', message) if m: line_no, error_message = m.groups() line_no = int(line_no) line_contents = text.splitlines()[int(line_no)-1].rstrip() r.description = error_message r.lineStart = r.lineEnd = line_no r.columnStart = len(line_contents) - len(line_contents.strip()) + 1 r.columnEnd = len(line_contents) + 1 return r
def _addMixedEOLWarnings(self, results, content, expectedEOL): """Add lint results (at the WARNING level) for each line that has an unexpected EOL. "results" in a koILintResults to which to add mixed EOL results. "content" is the content to analyze "expectedEOL" is the currently configured EOL for the document, this must be on of the EOL_LF, EOL_CR, EOL_CRLF constants. """ import eollib mixedEOLs = eollib.getMixedEOLLineNumbers(content, expectedEOL) if not mixedEOLs: return def collapseContinuousLineNumbers(lineNos): """Return a collapsed group of continuous line numbers.""" results = [] start = -10 last = -10 for lineNo in lineNos: if lineNo == last+1: pass else: if start >= 0: results.append((start, last)) start = lineNo last = lineNo if start >= 0: results.append((start, last)) return results # Add a warning lint result for each such line. expectedEOLStr = eollib.eol2eolPref[expectedEOL] lines = content.splitlines(1) # For performance reasons, we collapse groups of continuous line # numbers into the one line result - bug 92733. for lineStart, lineEnd in collapseContinuousLineNumbers(mixedEOLs): r = KoLintResult() r.description = "This line does not end with the expected "\ "EOL: '%s' (select View | View EOL Markers)"\ % expectedEOLStr r.lineStart = lineStart+1 r.lineEnd = lineEnd+1 r.columnStart = 1 r.columnEnd = len(lines[lineEnd]) + 1 r.severity = r.SEV_WARNING results.addResult(r)
def _addMixedEOLWarnings(self, results, content, expectedEOL): """Add lint results (at the WARNING level) for each line that has an unexpected EOL. "results" in a koILintResults to which to add mixed EOL results. "content" is the content to analyze "expectedEOL" is the currently configured EOL for the document, this must be on of the EOL_LF, EOL_CR, EOL_CRLF constants. """ import eollib mixedEOLs = eollib.getMixedEOLLineNumbers(content, expectedEOL) if not mixedEOLs: return def collapseContinuousLineNumbers(lineNos): """Return a collapsed group of continuous line numbers.""" results = [] start = -10 last = -10 for lineNo in lineNos: if lineNo == last + 1: pass else: if start >= 0: results.append((start, last)) start = lineNo last = lineNo if start >= 0: results.append((start, last)) return results # Add a warning lint result for each such line. expectedEOLStr = eollib.eol2eolPref[expectedEOL] lines = content.splitlines(1) # For performance reasons, we collapse groups of continuous line # numbers into the one line result - bug 92733. for lineStart, lineEnd in collapseContinuousLineNumbers(mixedEOLs): r = KoLintResult() r.description = "This line does not end with the expected "\ "EOL: '%s' (select View | View EOL Markers)"\ % expectedEOLStr r.lineStart = lineStart + 1 r.lineEnd = lineEnd + 1 r.columnStart = 1 r.columnEnd = len(lines[lineEnd]) + 1 r.severity = r.SEV_WARNING results.addResult(r)
def _buildResult(self, text, message, filename=None): r = KoLintResult() r.severity = r.SEV_ERROR r.description = message m = re.match('./(.+\.go):(\d+): (.*)', message) if m: lint_message_file, line_no, error_message = m.groups() if lint_message_file != filename: r.description = message return r line_no = int(line_no) line_contents = text.splitlines()[int(line_no)-1].rstrip() r.description = error_message r.lineStart = r.lineEnd = line_no r.columnStart = len(line_contents) - len(line_contents.strip()) + 1 r.columnEnd = len(line_contents) + 1 return r
def _buildResult(text, message, filename=None): r = KoLintResult() r.severity = r.SEV_ERROR r.description = message m = re.match('./(.+\.go):(\d+): (.*)', message) if m: lint_message_file, line_no, error_message = m.groups() if lint_message_file != filename: r.description = message return r line_no = int(line_no) log.debug("_buildResult %s %s", line_no, len(text.splitlines())) line_contents = text.splitlines()[int(line_no)-1].rstrip() r.description = error_message r.lineStart = r.lineEnd = line_no r.columnStart = len(line_contents) - len(line_contents.strip()) + 1 r.columnEnd = len(line_contents) + 1 return r
def _fixPerlPart(self, text): parts = self._masonMatcher.findall(text) if not parts: return "", [] i = 0 lim = len(parts) perlTextParts = [] masonLintResults = [] eols = ("\n", "\r\n") # states currTags = [] perlTags = ('init', 'perl', 'once') # drop code for other tags. lineNo = i while i < lim: part = parts[i] if part in eols: perlTextParts.append(part) elif part.startswith("%") and (i == 0 or parts[i - 1].endswith("\n")): m = self._perlLineRE.match(part) if not m: perlTextParts.append(self._spaceOutNonNewlines(part)) else: perlTextParts.append(self._spaceOutNonNewlines(m.group(1))) perlTextParts.append(m.group(2)) elif part.startswith("<"): m = self._blockTagRE.match(part) if m: payload = m.group(2) if m.group(1): unexpectedEndTag = None if currTags: currTag = currTags[-1] if currTag == payload: currTags.pop() else: # Recover by removing everything up to and including the tag unexpectedEndTag = currTags[-1] for idx in range(len(currTags) - 1, -1, -1): if currTags[idx] == payload: del currTags[idx:-1] break else: unexpectedEndTag = "not in a tag block" if unexpectedEndTag is not None: lr = KoLintResult() lr.lineStart = lr.lineEnd = lineNo + 1 lr.columnStart = 1 lr.columnEnd = 1 + len(text.splitlines()[lineNo]) if unexpectedEndTag == "not in a tag block": lr.description = "Got end tag %s, not in a tag" % part else: lr.description = "Expected </%%%s>, got %s" % ( unexpectedEndTag, part) lr.encodedDescription = lr.description lr.severity = lr.SEV_WARNING masonLintResults.append(lr) else: currTags.append(payload) perlTextParts.append(self._spaceOutNonNewlines(part)) else: m = self._exprRE.match(part) if not m: perlTextParts.append(self._spaceOutNonNewlines(part)) else: perlTextParts.append( self._spaceOutNonNewlines(m.group(1))) payload = m.group(2) if payload.startswith("#"): perlTextParts.append(payload) # One big comment elif "|" not in payload: perlTextParts.append("print " + payload + ";") else: # Filters aren't perl syntax, so punt perlTextParts.append( self._spaceOutNonNewlines(m.group(2))) perlTextParts.append( self._spaceOutNonNewlines(m.group(3))) else: # We only copy things out under certain circumstances if not currTags or currTags[-1] not in perlTags: perlTextParts.append(self._spaceOutNonNewlines(part)) else: perlTextParts.append(part) i += 1 lineNo += part.count("\n") return "".join(perlTextParts), masonLintResults
offset = 0 # the current offset in the document for i in range(len(lines)): line = lines[i] while indeces and indeces[0] < offset + len(line): index = indeces.pop(0) # this index is on this line r = KoLintResult() r.description = "This character cannot be represented with "\ "the current encoding: '%s'"\ % encoding.python_encoding_name r.lineStart = i+1 r.lineEnd = i+1 r.columnStart = index - offset + 1 r.columnEnd = r.columnStart + 1 log.debug("encoding error: index=%d: %d,%d-%d,%d", index, r.lineStart, r.columnStart, r.lineEnd, r.columnEnd) r.severity = r.SEV_ERROR results.addResult(r) if not indeces: break offset += len(line) else: raise ValueError("Did not find line and column for one or " "more indeces in content: %s" % indeces) return results def _addMixedEOLWarnings(self, results, content, expectedEOL): """Add lint results (at the WARNING level) for each line that has an unexpected EOL. "results" in a koILintResults to which to add mixed EOL results.
def _errorResult(description): r = KoLintResult() r.severity = r.SEV_ERROR r.description = description return r
def _jslint_with_text(self, request, text, prefSwitchName, prefOptionsName): if not text: #log.debug("<< no text") return prefset = request.prefset if not prefset.getBooleanPref(prefSwitchName): return jsfilename, isMacro, datalines = self._make_tempfile_from_text(request, text) jsInterp = self._get_js_interp_path() jsLintDir = os.path.join(self.koDirs.supportDir, "lint", "javascript") jsLintApp = os.path.join(jsLintDir, "lintWrapper.js") jsLintBasename = None if prefSwitchName == "lintWithJSLint": appName = "jslint" else: appName = "jshint" try: customJSLint = prefset.getStringPref(appName + "_linter_chooser") if customJSLint == "specific": p = prefset.getStringPref(appName + "_linter_specific") if p and os.path.exists(p): jsLintDir = os.path.dirname(p) + "/" jsLintBasename = os.path.basename(p) except: log.exception("Problem finding the custom lintjs file") options = prefset.getStringPref(prefOptionsName).strip() # Lint the temp file, the jsInterp options are described here: # https://developer.mozilla.org/en/Introduction_to_the_JavaScript_shell cmd = [jsInterp, jsLintApp, "--include=" + jsLintDir] if jsLintBasename: if prefSwitchName == "lintWithJSLint": cmd.append("--jslint-basename=" + jsLintBasename) else: cmd.append("--jshint-basename=" + jsLintBasename) elif prefSwitchName == "lintWithJSHint": cmd.append("--jshint") if options: # Drop empty parts. otherParts = [s for s in re.compile(r'\s+').split(options)] cmd += [s for s in re.compile(r'\s+').split(options)] if request.koDoc.language == "Node.js": if not "node=" in options: cmd.append("node=1") if (prefSwitchName == "lintWithJSHint" and not self.strict_option_re.match(options) and 'globalstrict=' not in options): # jshint tests options.strict !== false, otherwise strict is on # Other options are tested as simply !options.strict cmd.append('strict=false') fd = open(jsfilename) cwd = request.cwd or None # We only need the stderr result. try: p = process.ProcessOpen(cmd, cwd=cwd, env=self._setLDLibraryPath(), stdin=fd) stdout, stderr = p.communicate() if stderr: log.warn("Error in jslint/jshint: stderr: %s, command was: %s", stderr, cmd) #log.debug("jslint(%s): stdout: %s, stderr: %s", prefSwitchName, stdout, stderr) warnLines = stdout.splitlines() # Don't need the newlines. i = 0 outputStart = "++++JSLINT OUTPUT:" while i < len(warnLines): if outputStart in warnLines[i]: warnLines = warnLines[i + 1:] break i += 1 except: log.exception("Problem running GenericJSLinter") finally: try: fd.close() except: log.error("Problem closing file des(%s)", jsfilename) try: os.unlink(jsfilename) except: log.error("Problem deleting file des(%s)", jsfilename) # 'jslint' error reports come in this form: # jslint error: at line \d+ column \d+: explanation results = koLintResults() msgRe = re.compile("^jslint error: at line (?P<lineNo>\d+) column (?P<columnNo>\d+):\s*(?P<desc>.*?)$") numDataLines = len(datalines) if len(warnLines) % 2 == 1: warnLines.append("") for i in range(0, len(warnLines), 2): msgLine = warnLines[i] evidenceLine = warnLines[i + 1] m = msgRe.match(msgLine) if m: lineNo = int(m.group("lineNo")) #columnNo = int(m.group("columnNo")) # build lint result object result = KoLintResult() evidenceLineNo = lineNo if evidenceLineNo >= numDataLines: evidenceLineNo = numDataLines - 1 if evidenceLine in datalines[evidenceLineNo]: lineNo = evidenceLineNo pass elif evidenceLineNo > 0 and evidenceLine in datalines[evidenceLineNo - 1]: lineNo = evidenceLineNo - 1 elif lineNo >= numDataLines: lineNo = numDataLines - 1 # if the error is on the last line, work back to the last # character of the first nonblank line so we can display # the error somewhere if len(datalines[lineNo]) == 0: while lineNo > 0 and len(datalines[lineNo - 1]) == 0: lineNo -= 1 result.columnStart = 1 result.columnEnd = len(datalines[lineNo]) + 1 result.lineStart = result.lineEnd = lineNo + 1 result.severity = result.SEV_WARNING result.description = m.group("desc") results.addResult(result) return results
def _fixPerlPart(self, text): parts = self._masonMatcher.findall(text) if not parts: return "", [] i = 0 lim = len(parts) perlTextParts = [] masonLintResults = [] eols = ("\n", "\r\n") # states currTags = [] perlTags = ('init', 'perl', 'once') # drop code for other tags. lineNo = i while i < lim: part = parts[i] if part in eols: perlTextParts.append(part) elif part.startswith("%") and (i == 0 or parts[i - 1].endswith("\n")): m = self._perlLineRE.match(part) if not m: perlTextParts.append(self._spaceOutNonNewlines(part)) else: perlTextParts.append(self._spaceOutNonNewlines(m.group(1))) perlTextParts.append(m.group(2)) elif part.startswith("<"): m = self._blockTagRE.match(part) if m: payload = m.group(2) if m.group(1): unexpectedEndTag = None if currTags: currTag = currTags[-1] if currTag == payload: currTags.pop() else: # Recover by removing everything up to and including the tag unexpectedEndTag = currTags[-1] for idx in range(len(currTags) - 1, -1, -1): if currTags[idx] == payload: del currTags[idx:-1] break else: unexpectedEndTag = "not in a tag block" if unexpectedEndTag is not None: lr = KoLintResult() lr.lineStart = lr.lineEnd = lineNo + 1 lr.columnStart = 1 lr.columnEnd = 1 + len(text.splitlines()[lineNo]) if unexpectedEndTag == "not in a tag block": lr.description = "Got end tag %s, not in a tag" % part else: lr.description = "Expected </%%%s>, got %s" % ( unexpectedEndTag, part) lr.encodedDescription = lr.description lr.severity = lr.SEV_WARNING masonLintResults.append(lr) else: currTags.append(payload) perlTextParts.append(self._spaceOutNonNewlines(part)) else: m = self._exprRE.match(part) if not m: perlTextParts.append(self._spaceOutNonNewlines(part)) else: perlTextParts.append(self._spaceOutNonNewlines(m.group(1))) payload = m.group(2) if payload.startswith("#"): perlTextParts.append(payload) # One big comment elif "|" not in payload: perlTextParts.append("print " + payload + ";"); else: # Filters aren't perl syntax, so punt perlTextParts.append(self._spaceOutNonNewlines(m.group(2))) perlTextParts.append(self._spaceOutNonNewlines(m.group(3))) else: # We only copy things out under certain circumstances if not currTags or currTags[-1] not in perlTags: perlTextParts.append(self._spaceOutNonNewlines(part)) else: perlTextParts.append(part) i += 1 lineNo += part.count("\n") return "".join(perlTextParts), masonLintResults
def _TclPro_lint(self, lInput, lOutput): # TclPro output has the following header info: ################### #TclPro -- Version 1.4.1 #Copyright (C) Ajuba Solutions 1998-2001. All rights reserved. #This product is registered to: <user name> # #scanning: <filename> #checking: <filename> #<filename>:<lineNo> (<errorName>) <errorText> #<commandWithError> #<caret at col in command where error is> #[repeat above three until no more errors] ################### # The 'checking' line exists when the -onepass option isn't used i = 1 lastCol = -1 lastLine = -1 results = koLintResults() numLines = len(lOutput) while i < numLines: match = re.search(self._tclProRe, lOutput[i]) if not match: # We expect the first few lines to not match, while we # skip the header lines i += 1 # But if too many lines aren't matching, then we must have # unrecognizable input if i > 8: log.warn("couldn't recognize Tcl linter output format") break continue if i+2 >= numLines: log.error("unexpected return data format: short data") return cmd = lOutput[i+1].rstrip() col = lOutput[i+2].find('^') if col == -1: if i+3 == numLines: log.error("unexpected return data format: short data") return col = lOutput[i+3].find('^') if col == -1: log.warn("No column indicator following line %d:%r", i, lOutput[i]) i += 1 continue cmd = lOutput[i+2].rstrip() parseLineOffset = 1 else: parseLineOffset = 0 lineNo = match.group(1) # lInput is zero-based, whereas text display lines start at one, # so we adjust lineNo down one here. lineNo = int(lineNo) - 1 if lineNo != lastLine: lastLine = lineNo lastCol = 0 if col == -1: col = len(lInput[lineNo].rstrip()) startCol = lInput[lineNo].find(cmd, lastCol) if startCol == -1: # It's possible that we are throwing multiple warnings on # the same command. Let's check again, starting at col 0. # If something was found, ignore multiple warnings in the # same line, otherwise log a problem if lastCol == 0 or \ lInput[lineNo].find(cmd, 0) == -1: # Looks like we can't figure something out... log.debug('undecipherable lint output: %s,%s "%s" in "%s" last %s' \ % (lineNo+1, col, cmd, lInput[lineNo], lastCol)) i += parseLineOffset + 3 continue #print 'TclLint: %s,%s "%s" in "%s"' \ # % (lineNo+1, startCol, cmd, lInput[lineNo]) result = KoLintResult() msg = match.group(2) result.description = match.group(3).strip() result.lineStart = lineNo+1 # All our warnings stay on one line result.lineEnd = result.lineStart cmdLen = len(cmd) if msg.startswith("warn") or msg.startswith("nonPort"): result.severity = result.SEV_WARNING # In the warning case, go to the next char that we think # ends the word (not precise), starting at the caret. endCol = col while endCol < cmdLen and cmd[endCol] not in endWordSet: # might want to handle \ escapes endCol += 1 # readjust for where the command started endCol += startCol # and bump up the startCol to where the caret indicated startCol += col else: result.severity = result.SEV_ERROR # In the error case, highlight the entire command # The linter should only give one error per command endCol = startCol + cmdLen # Adjust by 1 as columns start at 1 for the text display result.columnStart = startCol + 1 result.columnEnd = endCol + 1 results.addResult(result) # Set lastCol to properly handle multiple similar code-blocks # with errors on the same line. lastCol = startCol+1 i += 3 # end while return results
def PerlWarnsToLintResults(warns, perlfilename, actualFileName, perlcode): perllines = perlcode.split('\n') # preprocess the perl warnings # - copy up '(Missing semicolon on previous line?)' to previous line # - remove 'perlfilename' #XXX it would be nice to find all hints and copy them up newwarns = [] for line in warns: # get rid if eols line = line[:-1] # handle multi-line string literals if newwarns and newwarns[-1] and newwarns[-1][-1] == "\r" : #XXX we should print an EOL symbol newwarns[-1] = newwarns[-1][:-1] + '\r' + line ## this code is not handling hints correctly. In any case, hints make ## the error messages too long for the status bar #elif line.find('(Missing semicolon on previous line?)') != -1: # newwarns[-1] = newwarns[-1] + " " + line[1:] else: newline = line newwarns.append(newline) warns = newwarns results = [] if type(perlfilename) == unicode: perlfilename = perlfilename.encode('utf-8') escPerlName = re.escape(perlfilename) # Fix bug 96303 (again): Replace any runs of non-ascii characters with unescaped '.*' # The other parts of the filename do get escaped. perlfilename_ascii_parts = re.split(r'([^\x00-\x7f]+)', perlfilename) escaped_even_parts = [re.escape(part) for part in perlfilename_ascii_parts[0::2]] escPerlName = ".*?".join(escaped_even_parts) warnRe = re.compile(r'(?P<description>.*) at %s line (?P<lineNum>\d+)(?P<hint>.*)' % escPerlName) successRe = re.compile(r'%s syntax OK' % escPerlName) compilationFailedRe = re.compile(r'Compilation failed in require at %s' % escPerlName) compilationErrorsRe = re.compile(r'%s had compilation errors\.' % escPerlName) beginFailedRe = re.compile(r'BEGIN failed--compilation aborted') pendingLines = [] sawSyntaxOK = False sawSyntaxBad = False for warn in warns: if successRe.match(warn): sawSyntaxOK = True continue match = warnRe.search(warn) if match: if compilationFailedRe.match(warn) or beginFailedRe.match(warn) or compilationErrorsRe.match(warn): sawSyntaxBad = True if results: # Don't bother displaying this continue lineNum = int(match.group('lineNum')) varName = match.groupdict().get('varName', None) lr = KoLintResult() lr.description = _combineTextParts(pendingLines, match.group('description') + match.group('hint')).replace(perlfilename, actualFileName) pendingLines = [] while lr.description.endswith("\n"): lr.description = lr.description[:-1] lr.lineStart = lineNum lr.lineEnd = lineNum if varName: lr.columnStart = perllines[lr.lineStart-1].find(varName) + 1 if lr.columnStart == -1: lr.columnEnd = -1 else: lr.columnEnd = lr.columnStart + len(varName) + 1 else: lr.columnStart = 1 lr.columnEnd = len(perllines[lr.lineStart-1]) + 1 results.append(lr) else: pendingLines.append(warn) if pendingLines and results: results[-1].description = _combineTextParts(pendingLines, results[-1].description).replace(perlfilename, actualFileName) # XXX # # Whatever is caught by the next else block is (according to Aaron) additional information # re: the last error. It should be added to the lint warning, but isn't because the # status bar display isn't smart enough and it looks ugly. # Todo: 1) add the extra info to the lint description (possibly add a new attribute to LintResults # with this 'metadata' # 2) Update the status bar display and tooltip display to display the right info. # I'm commenting this block out because log.warn's impact speed. --david # # else: # if not (successRe.search(warn) or failureRe.search(warn)): # log.warn("Skipping Perl warning: %r", warn) # under some conditions, Perl will spit out messages after the # "syntax Ok", so check the entire output. if results: if sawSyntaxOK and not sawSyntaxBad: severity = KoLintResult.SEV_WARNING elif sawSyntaxBad and not sawSyntaxOK: severity = KoLintResult.SEV_ERROR elif successRe.search("\n".join(warns)): severity = KoLintResult.SEV_WARNING else: severity = KoLintResult.SEV_ERROR lintResults = koLintResults() for lr in results: lr.severity = severity lintResults.addResult(lr) return lintResults
def lint_with_text(self, request, text): if not text.strip(): return None cwd = request.cwd env = koprocessutils.getUserEnv() settingsDir = env.get("DJANGO_SETTINGS_MODULE", None) if not settingsDir: # Django wants to do something like "import project.settings", which # means "project/settings.py" needs to exist. First, try to find it. settingsDir = self._getSettingsDir(cwd) # Ultimately, Komodo's Django linter (djangoLinter.py) sets the # DJANGO_SETTINGS_MODULE env variable to be the basename of # "settingsDir", which needs to be a module in the PYTHONPATH (which # the linter will append the dirname of "settingsDir" to). Append # ".settings" so when Django tries to do something like # "import project.settings", it will behave as expected. settingsDir += ".settings" if settingsDir: # Save the current buffer to a temporary file. tmpFileName = tempfile.mktemp() fout = open(tmpFileName, 'wb') try: fout.write(text) fout.close() #XXX: How to tell whether we're using Python or Python3? prefName = "pythonExtraPaths" pythonPath = request.prefset.getString(prefName, "") pythonPathEnv = env.get("PYTHONPATH", "") if pythonPathEnv: if pythonPath: pythonPath += os.pathsep + pythonPathEnv else: pythonPath = pythonPathEnv if pythonPath: if sys.platform.startswith("win"): pythonPath = pythonPath.replace('\\', '/') env["PYTHONPATH"] = pythonPath elif env.has_key("PYTHONPATH"): del env["PYTHONPATH"] # First try to use Python2 to run the django linter. If django # is not found, use Python3 instead. results = koLintResults() pythonExe = self._pythonInfo.getExecutableFromDocument( request.koDoc) djangoLinterPath = self._djangoLinterPath if pythonExe: p = process.ProcessOpen([pythonExe, "-c", "import django"], env=env, stdin=None) output, error = p.communicate() #log.debug("Django output: output:[%s], error:[%s]", output, error) if error.find('ImportError:') >= 0 and \ self._python3Info.getExecutableFromDocument(request.koDoc): pythonExe = self._python3Info.getExecutableFromDocument( request.koDoc) djangoLinterPath = self._djangoLinter3Path else: pythonExe = self._python3Info.getExecutableFromDocument( request.koDoc) djangoLinterPath = self._djangoLinter3Path #log.debug("pythonExe = " + pythonExe) #log.debug("djangoLinterPath = " + djangoLinterPath) argv = [pythonExe, djangoLinterPath, tmpFileName, settingsDir] p = process.ProcessOpen(argv, cwd=cwd, env=env, stdin=None) output, error = p.communicate() retval = p.returncode #log.debug("Django output: output:[%s], error:[%s], retval:%d", output, error) finally: os.unlink(tmpFileName) if error: results.addResult(self._buildResult(text, error)) elif retval != 0: results.addResult(self._buildResult(text, "Unexpected error")) else: result = KoLintResult() result.lineStart = 1 result.lineEnd = 1 result.columnStart = 1 result.columnEnd = 1 + len(text.splitlines(1)[0]) result.description = "Can't find settings.py for this Django file" result.encodedDescription = result.description result.severity = result.SEV_ERROR results = koLintResults() results.addResult(result) return results
offset = 0 # the current offset in the document for i in range(len(lines)): line = lines[i] while indeces and indeces[0] < offset + len(line): index = indeces.pop(0) # this index is on this line r = KoLintResult() r.description = "This character cannot be represented with "\ "the current encoding: '%s'"\ % encoding.python_encoding_name r.lineStart = i + 1 r.lineEnd = i + 1 r.columnStart = index - offset + 1 r.columnEnd = r.columnStart + 1 log.debug("encoding error: index=%d: %d,%d-%d,%d", index, r.lineStart, r.columnStart, r.lineEnd, r.columnEnd) r.severity = r.SEV_ERROR results.addResult(r) if not indeces: break offset += len(line) else: raise ValueError("Did not find line and column for one or " "more indeces in content: %s" % indeces) return results def _addMixedEOLWarnings(self, results, content, expectedEOL): """Add lint results (at the WARNING level) for each line that has an unexpected EOL. "results" in a koILintResults to which to add mixed EOL results.
def PerlWarnsToLintResults(warns, perlfilename, actualFileName, perlcode): perllines = perlcode.split('\n') # preprocess the perl warnings # - copy up '(Missing semicolon on previous line?)' to previous line # - remove 'perlfilename' #XXX it would be nice to find all hints and copy them up newwarns = [] for line in warns: # get rid if eols line = line[:-1] # handle multi-line string literals if newwarns and newwarns[-1] and newwarns[-1][-1] == "\r": #XXX we should print an EOL symbol newwarns[-1] = newwarns[-1][:-1] + '\r' + line ## this code is not handling hints correctly. In any case, hints make ## the error messages too long for the status bar #elif line.find('(Missing semicolon on previous line?)') != -1: # newwarns[-1] = newwarns[-1] + " " + line[1:] else: newline = line newwarns.append(newline) warns = newwarns results = [] if type(perlfilename) == unicode: perlfilename = perlfilename.encode('utf-8') escPerlName = re.escape(perlfilename) # Fix bug 96303 (again): Replace any runs of non-ascii characters with unescaped '.*' # The other parts of the filename do get escaped. perlfilename_ascii_parts = re.split(r'([^\x00-\x7f]+)', perlfilename) escaped_even_parts = [ re.escape(part) for part in perlfilename_ascii_parts[0::2] ] escPerlName = ".*?".join(escaped_even_parts) warnRe = re.compile( r'(?P<description>.*) at %s line (?P<lineNum>\d+)(?P<hint>.*)' % escPerlName) successRe = re.compile(r'%s syntax OK' % escPerlName) compilationFailedRe = re.compile(r'Compilation failed in require at %s' % escPerlName) compilationErrorsRe = re.compile(r'%s had compilation errors\.' % escPerlName) beginFailedRe = re.compile(r'BEGIN failed--compilation aborted') pendingLines = [] sawSyntaxOK = False sawSyntaxBad = False for warn in warns: if successRe.match(warn): sawSyntaxOK = True continue match = warnRe.search(warn) if match: if compilationFailedRe.match(warn) or beginFailedRe.match( warn) or compilationErrorsRe.match(warn): sawSyntaxBad = True if results: # Don't bother displaying this continue lineNum = int(match.group('lineNum')) varName = match.groupdict().get('varName', None) lr = KoLintResult() lr.description = _combineTextParts( pendingLines, match.group('description') + match.group('hint')).replace( perlfilename, actualFileName) pendingLines = [] while lr.description.endswith("\n"): lr.description = lr.description[:-1] lr.lineStart = lineNum lr.lineEnd = lineNum if varName: lr.columnStart = perllines[lr.lineStart - 1].find(varName) + 1 if lr.columnStart == -1: lr.columnEnd = -1 else: lr.columnEnd = lr.columnStart + len(varName) + 1 else: lr.columnStart = 1 lr.columnEnd = len(perllines[lr.lineStart - 1]) + 1 results.append(lr) else: pendingLines.append(warn) if pendingLines and results: results[-1].description = _combineTextParts( pendingLines, results[-1].description).replace(perlfilename, actualFileName) # XXX # # Whatever is caught by the next else block is (according to Aaron) additional information # re: the last error. It should be added to the lint warning, but isn't because the # status bar display isn't smart enough and it looks ugly. # Todo: 1) add the extra info to the lint description (possibly add a new attribute to LintResults # with this 'metadata' # 2) Update the status bar display and tooltip display to display the right info. # I'm commenting this block out because log.warn's impact speed. --david # # else: # if not (successRe.search(warn) or failureRe.search(warn)): # log.warn("Skipping Perl warning: %r", warn) # under some conditions, Perl will spit out messages after the # "syntax Ok", so check the entire output. if results: if sawSyntaxOK and not sawSyntaxBad: severity = KoLintResult.SEV_WARNING elif sawSyntaxBad and not sawSyntaxOK: severity = KoLintResult.SEV_ERROR elif successRe.search("\n".join(warns)): severity = KoLintResult.SEV_WARNING else: severity = KoLintResult.SEV_ERROR lintResults = koLintResults() for lr in results: lr.severity = severity lintResults.addResult(lr) return lintResults
def _jslint_with_text(self, request, text, prefSwitchName, prefOptionsName): if not text: #log.debug("<< no text") return prefset = request.prefset if not prefset.getBooleanPref(prefSwitchName): return jsfilename, isMacro, datalines = self._make_tempfile_from_text( request, text) jsInterp = self._get_js_interp_path() jsLintDir = os.path.join(self.koDirs.supportDir, "lint", "javascript") jsLintApp = os.path.join(jsLintDir, "lintWrapper.js") jsLintBasename = None if prefSwitchName == "lintWithJSLint": appName = "jslint" else: appName = "jshint" try: customJSLint = prefset.getStringPref(appName + "_linter_chooser") if customJSLint == "specific": p = prefset.getStringPref(appName + "_linter_specific") if p and os.path.exists(p): jsLintDir = os.path.dirname(p) + "/" jsLintBasename = os.path.basename(p) except: log.exception("Problem finding the custom lintjs file") options = prefset.getStringPref(prefOptionsName).strip() # Lint the temp file, the jsInterp options are described here: # https://developer.mozilla.org/en/Introduction_to_the_JavaScript_shell cmd = [jsInterp, jsLintApp, "--include=" + jsLintDir] if jsLintBasename: if prefSwitchName == "lintWithJSLint": cmd.append("--jslint-basename=" + jsLintBasename) else: cmd.append("--jshint-basename=" + jsLintBasename) elif prefSwitchName == "lintWithJSHint": cmd.append("--jshint") if options: # Drop empty parts. otherParts = [s for s in re.compile(r'\s+').split(options)] cmd += [s for s in re.compile(r'\s+').split(options)] if request.koDoc.language == "Node.js": if not "node=" in options: cmd.append("node=1") if (prefSwitchName == "lintWithJSHint" and not self.strict_option_re.match(options) and 'globalstrict=' not in options): # jshint tests options.strict !== false, otherwise strict is on # Other options are tested as simply !options.strict cmd.append('strict=false') fd = open(jsfilename) cwd = request.cwd or None # We only need the stderr result. try: p = process.ProcessOpen(cmd, cwd=cwd, env=self._setLDLibraryPath(), stdin=fd) stdout, stderr = p.communicate() if stderr: log.warn("Error in jslint/jshint: stderr: %s, command was: %s", stderr, cmd) #log.debug("jslint(%s): stdout: %s, stderr: %s", prefSwitchName, stdout, stderr) warnLines = stdout.splitlines() # Don't need the newlines. i = 0 outputStart = "++++JSLINT OUTPUT:" while i < len(warnLines): if outputStart in warnLines[i]: warnLines = warnLines[i + 1:] break i += 1 except: log.exception("Problem running GenericJSLinter") finally: try: fd.close() except: log.error("Problem closing file des(%s)", jsfilename) try: os.unlink(jsfilename) except: log.error("Problem deleting file des(%s)", jsfilename) # 'jslint' error reports come in this form: # jslint error: at line \d+ column \d+: explanation results = koLintResults() msgRe = re.compile( "^jslint error: at line (?P<lineNo>\d+) column (?P<columnNo>\d+):\s*(?P<desc>.*?)$" ) numDataLines = len(datalines) if len(warnLines) % 2 == 1: warnLines.append("") for i in range(0, len(warnLines), 2): msgLine = warnLines[i] evidenceLine = warnLines[i + 1] m = msgRe.match(msgLine) if m: lineNo = int(m.group("lineNo")) #columnNo = int(m.group("columnNo")) # build lint result object result = KoLintResult() evidenceLineNo = lineNo if evidenceLineNo >= numDataLines: evidenceLineNo = numDataLines - 1 if evidenceLine in datalines[evidenceLineNo]: lineNo = evidenceLineNo pass elif evidenceLineNo > 0 and evidenceLine in datalines[ evidenceLineNo - 1]: lineNo = evidenceLineNo - 1 elif lineNo >= numDataLines: lineNo = numDataLines - 1 # if the error is on the last line, work back to the last # character of the first nonblank line so we can display # the error somewhere if len(datalines[lineNo]) == 0: while lineNo > 0 and len(datalines[lineNo - 1]) == 0: lineNo -= 1 result.columnStart = 1 result.columnEnd = len(datalines[lineNo]) + 1 result.lineStart = result.lineEnd = lineNo + 1 result.severity = result.SEV_WARNING result.description = m.group("desc") results.addResult(result) return results
def lint_with_text(self, request, text): if not text.strip(): return None cwd = request.cwd env = koprocessutils.getUserEnv() settingsDir = env.get("DJANGO_SETTINGS_MODULE", None) if not settingsDir: # Django wants to do something like "import project.settings", which # means "project/settings.py" needs to exist. First, try to find it. settingsDir = self._getSettingsDir(cwd) # Ultimately, Komodo's Django linter (djangoLinter.py) sets the # DJANGO_SETTINGS_MODULE env variable to be the basename of # "settingsDir", which needs to be a module in the PYTHONPATH (which # the linter will append the dirname of "settingsDir" to). Append # ".settings" so when Django tries to do something like # "import project.settings", it will behave as expected. settingsDir += ".settings" if settingsDir: # Save the current buffer to a temporary file. tmpFileName = tempfile.mktemp() fout = open(tmpFileName, 'wb') try: fout.write(text) fout.close() #XXX: How to tell whether we're using Python or Python3? prefName = "pythonExtraPaths" pythonPath = request.prefset.getString(prefName, "") pythonPathEnv = env.get("PYTHONPATH", "") if pythonPathEnv: if pythonPath: pythonPath += os.pathsep + pythonPathEnv else: pythonPath = pythonPathEnv if pythonPath: if sys.platform.startswith("win"): pythonPath = pythonPath.replace('\\', '/') env["PYTHONPATH"] = pythonPath elif env.has_key("PYTHONPATH"): del env["PYTHONPATH"] # First try to use Python2 to run the django linter. If django # is not found, use Python3 instead. results = koLintResults() pythonExe = self._pythonInfo.getExecutableFromDocument(request.koDoc) djangoLinterPath = self._djangoLinterPath if pythonExe: p = process.ProcessOpen([pythonExe, "-c", "import django"], env=env, stdin=None) output, error = p.communicate() #log.debug("Django output: output:[%s], error:[%s]", output, error) if error.find('ImportError:') >= 0 and \ self._python3Info.getExecutableFromDocument(request.koDoc): pythonExe = self._python3Info.getExecutableFromDocument(request.koDoc) djangoLinterPath = self._djangoLinter3Path else: pythonExe = self._python3Info.getExecutableFromDocument(request.koDoc) djangoLinterPath = self._djangoLinter3Path #log.debug("pythonExe = " + pythonExe) #log.debug("djangoLinterPath = " + djangoLinterPath) argv = [pythonExe, djangoLinterPath, tmpFileName, settingsDir] p = process.ProcessOpen(argv, cwd=cwd, env=env, stdin=None) output, error = p.communicate() retval = p.returncode #log.debug("Django output: output:[%s], error:[%s], retval:%d", output, error) finally: os.unlink(tmpFileName) if error: results.addResult(self._buildResult(text, error)) elif retval != 0: results.addResult(self._buildResult(text, "Unexpected error")) else: result = KoLintResult() result.lineStart = 1 result.lineEnd = 1 result.columnStart = 1 result.columnEnd = 1 + len(text.splitlines(1)[0]) result.description = "Can't find settings.py for this Django file" result.encodedDescription = result.description result.severity = result.SEV_ERROR results = koLintResults() results.addResult(result) return results