Community
    • Login

    PHP Linter PythonScript

    Scheduled Pinned Locked Moved General Discussion
    1 Posts 1 Posters 59 Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • pbarneyP
      pbarney
      last edited by

      If you happen to write PHP code, it can be helpful to run PHP’s built-in linter (syntax checker) on your code (php -r file.php).

      If you have the NP++ PythonScript plugin installed, you can use this script to do the job. I assigned it to a hotkey to make it quick and easy to run.

      Honestly, it seems like this should be a simple thing to do, but it needs a lot of scaffolding to handle all the edge cases (saved/unsaved file, command line argument quoting, font encoding, etc).

      Positive and negative feedback is very much appreciated.

      # -*- coding: utf-8 -*-
      # phpLinter.py - Runs PHP's built-in syntax checker on the current 
      #     Notepad++ file and shows the results in the PythonScript console. 
      #     If the linter reports an error, the editor's cursor will be move 
      #     to the specified line.
      #
      # - Your file must be saved (you'll be prompted to save it if it's not).
      # - php.exe must be in your PATH.
      # - Works with the PythonScript plugin's Python 2.7 runtime
      # - If you set ASK_BEFORE_SAVE to False, the script will automatically 
      #   save your file prior to linting.
      
      import subprocess
      import re
      import os
      import sys
      import locale
      from Npp import *
      
      # --- user prefs ---
      NEWLINE_MODE = "remove"   # how to modify PHP output; options: "keep", "collapse", "remove"
      ASK_BEFORE_SAVE = True    # True = message box before saving
      DISPLAY_RESULT = False    # True = show result after PHP output
      # -------------------
      
      # --- enum fallbacks for older PythonScript builds ---
      try:
          IDYES = MESSAGEBOXRESULTS.IDYES
          IDNO  = MESSAGEBOXRESULTS.IDNO
      except Exception:
          IDYES, IDNO = 6, 7
      
      try:
          MB_YESNO = MESSAGEBOXFLAGS.YESNO
          MB_ICONQ = MESSAGEBOXFLAGS.ICONQUESTION
      except Exception:
          MB_YESNO, MB_ICONQ = 0x00000004, 0x00000020
      # ----------------------------------------------------
      
      def _nl(s=u""):
          console.write((s if isinstance(s, unicode) else s.decode('utf-8','replace')) + u"\r\n")
      
      def _quote_arg(arg):
          # simple Windows/posix-safe echo quoting for display only
          if isinstance(arg, unicode):
              a = arg
          else:
              try:
                  a = arg.decode('utf-8')
              except Exception:
                  a = unicode(arg, errors='replace')
          if re.search(ur'[\s"]', a):
              return u'"' + a.replace(u'"', u'\\"') + u'"'
          return a
      
      def _to_unicode(b):
          # convert subprocess output to unicode safely under Py2
          if isinstance(b, unicode):
              return b
          enc = locale.getpreferredencoding() or 'utf-8'
          try:
              return b.decode(enc, 'replace')
          except Exception:
              return b.decode('utf-8', 'replace')
      
      def normalize_output(s, mode):
          if mode == "keep":
              return s
          if mode == "remove":
              # delete any line that is empty or whitespace-only
              return re.sub(ur'(?m)^[ \t]*\r?\n', u'', s)
          if mode == "collapse":
              # collapse runs of blank/whitespace-only lines to a single newline
              return re.sub(ur'(?m)(?:^[ \t]*\r?\n){2,}', u'\r\n', s)
          # fallback
          return s
      
      def run_php_lint():
          path = notepad.getCurrentFilename()
          is_real_file = path and os.path.isfile(path)
      
          # ask before saving (optional)
          if ASK_BEFORE_SAVE and editor.getModify():
              ans = notepad.messageBox(
                  "Save the current file before linting?",
                  "PHP Lint",
                  MB_YESNO | MB_ICONQ
              )
              if ans != IDYES:
                  console.show()
                  _nl(u"Cancelled: file not saved, lint not run.")
                  return
      
          # save (will open "Save As..." for new/unsaved docs)
          notepad.save()
      
          # re-evaluate path after save (user may have cancelled Save As)
          path = notepad.getCurrentFilename()
          if not path or not os.path.isfile(path):
              console.show()
              _nl(u"No saved file to lint (did you cancel Save As?)")
              return
      
          # make sure we use a unicode path for display and pass bytes to Popen
          cmd = ["php", "-l", path]
      
          try:
              p = subprocess.Popen(
                  cmd,
                  stdout=subprocess.PIPE,
                  stderr=subprocess.PIPE,
                  shell=False
              )
              out_b, err_b = p.communicate()
              rc = p.returncode
          except Exception as e:
              console.show()
              _nl(u"Failed to execute php -l:")
              _nl(unicode(e))
              return
      
          raw = _to_unicode(out_b or u"") + _to_unicode(err_b or u"")
          output = normalize_output(raw, NEWLINE_MODE)
      
          console.show()
          _nl(u"-" * 80)
      
          # show the command we ran
          _nl(u">>> " + u" ".join([_quote_arg(a if isinstance(a, unicode) else a.decode('utf-8','ignore')) for a in cmd]))
      
          if output.strip():
              # write exactly what PHP printed
              console.write(output if isinstance(output, unicode) else _to_unicode(output))
      
          # result summary
          if DISPLAY_RESULT:
              prefix = u"" if NEWLINE_MODE == "remove" else u"\n"
              if rc == 0:
                  _nl(prefix + u"Result: OK (exit code 0)")
              elif rc == 255:
                  _nl(prefix + u"Result: Syntax error (exit code 255)")
              else:
                  _nl(prefix + u"Result: php -l exited with code {0}".format(rc))
      
          # jump to the reported line if present
          m = re.search(ur'on line (\d+)', raw)
          if m:
              try:
                  line = int(m.group(1)) - 1
                  if line >= 0:
                      editor.gotoLine(line)
              except Exception:
                  pass
      
      if __name__ == '__main__':
          run_php_lint()
      
      # give keyboard focus back to the editing pane
      editor.grabFocus()
      
      1 Reply Last reply Reply Quote 0
      • First post
        Last post
      The Community of users of the Notepad++ text editor.
      Powered by NodeBB | Contributors