Bug 1215238 - Mention the included filepath in pre-processed js sources with #includes. r=glandium
authorChris Manchester <cmanchester@mozilla.com>
Fri, 06 Nov 2015 15:58:30 -0800
changeset 271623 d419fd3f62efa8fe6b617a9e097e01d1ddb9e4fb
parent 271622 ac5202696eb28fa7bac0a1ea77dd75258cec3987
child 271624 cda8dacfd61bfac5dbe38e89838ce5e11ad14c0a
push id67711
push usercmanchester@mozilla.com
push dateFri, 06 Nov 2015 23:58:36 +0000
treeherdermozilla-inbound@d419fd3f62ef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersglandium
bugs1215238
milestone45.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1215238 - Mention the included filepath in pre-processed js sources with #includes. r=glandium
python/mozbuild/mozbuild/preprocessor.py
python/mozbuild/mozbuild/test/test_preprocessor.py
--- a/python/mozbuild/mozbuild/preprocessor.py
+++ b/python/mozbuild/mozbuild/preprocessor.py
@@ -283,17 +283,16 @@ class Preprocessor:
         self.actionLevel = 0
         self.disableLevel = 0
         # ifStates can be
         #  0: hadTrue
         #  1: wantsTrue
         #  2: #else found
         self.ifStates = []
         self.checkLineNumbers = False
-        self.writtenLines = 0
         self.filters = []
         self.cmds = {}
         for cmd, level in {'define': 0,
                            'undef': 0,
                            'if': sys.maxint,
                            'ifdef': sys.maxint,
                            'ifndef': sys.maxint,
                            'else': 1,
@@ -398,30 +397,38 @@ class Preprocessor:
         finally:
             self.out = old_out
 
     def applyFilters(self, aLine):
         for f in self.filters:
             aLine = f[1](aLine)
         return aLine
 
+    def noteLineInfo(self):
+        # Record the current line and file. Called once before transitioning
+        # into or out of an included file and after writing each line.
+        self.line_info = self.context['FILE'], self.context['LINE']
+
     def write(self, aLine):
         """
         Internal method for handling output.
         """
         if not self.out:
             return
 
+        next_line, next_file = self.context['LINE'], self.context['FILE']
         if self.checkLineNumbers:
-            self.writtenLines += 1
-            ln = self.context['LINE']
-            if self.writtenLines != ln:
-                self.out.write('//@line {line} "{file}"\n'.format(line=ln,
-                                                                  file=self.context['FILE']))
-                self.writtenLines = ln
+            expected_file, expected_line = self.line_info
+            expected_line += 1
+            if (expected_line != next_line or
+                expected_file and expected_file != next_file):
+                self.out.write('//@line {line} "{file}"\n'.format(line=next_line,
+                                                                  file=next_file))
+        self.noteLineInfo()
+
         filteredLine = self.applyFilters(aLine)
         if filteredLine != aLine:
             self.actionLevel = 2
         self.out.write(filteredLine)
 
     def handleCommandLine(self, args, defaultToStdin = False):
         """
         Parse a commandline into this parser.
@@ -536,17 +543,16 @@ class Preprocessor:
                 raise Preprocessor.Error(self, 'INVALID_CMD', aLine)
             level, cmd = self.cmds[cmd]
             if (level >= self.disableLevel):
                 cmd(args)
             if cmd != 'literal':
                 self.actionLevel = 2
         elif self.disableLevel == 0 and not self.comment.match(aLine):
             self.write(aLine)
-        pass
 
     # Instruction handlers
     # These are named do_'instruction name' and take one argument
 
     # Variables
     def do_define(self, args):
         m = re.match('(?P<name>\w+)(?:\s(?P<value>.*))?', args, re.U)
         if not m:
@@ -725,17 +731,16 @@ class Preprocessor:
     # File ops
     def do_include(self, args, filters=True):
         """
         Preprocess a given file.
         args can either be a file name, or a file-like object.
         Files should be opened, and will be closed after processing.
         """
         isName = type(args) == str or type(args) == unicode
-        oldWrittenLines = self.writtenLines
         oldCheckLineNumbers = self.checkLineNumbers
         self.checkLineNumbers = False
         if isName:
             try:
                 args = str(args)
                 if filters:
                     args = self.applyFilters(args)
                 if not os.path.isabs(args):
@@ -744,35 +749,37 @@ class Preprocessor:
             except Preprocessor.Error:
                 raise
             except:
                 raise Preprocessor.Error(self, 'FILE_NOT_FOUND', str(args))
         self.checkLineNumbers = bool(re.search('\.(js|jsm|java)(?:\.in)?$', args.name))
         oldFile = self.context['FILE']
         oldLine = self.context['LINE']
         oldDir = self.context['DIRECTORY']
+        self.noteLineInfo()
+
         if args.isatty():
             # we're stdin, use '-' and '' for file and dir
             self.context['FILE'] = '-'
             self.context['DIRECTORY'] = ''
         else:
             abspath = os.path.abspath(args.name)
             self.includes.add(abspath)
             self.context['FILE'] = abspath
             self.context['DIRECTORY'] = os.path.dirname(abspath)
         self.context['LINE'] = 0
-        self.writtenLines = 0
+
         for l in args:
             self.context['LINE'] += 1
             self.handleLine(l)
         if isName:
             args.close()
+
         self.context['FILE'] = oldFile
         self.checkLineNumbers = oldCheckLineNumbers
-        self.writtenLines = oldWrittenLines
         self.context['LINE'] = oldLine
         self.context['DIRECTORY'] = oldDir
     def do_includesubst(self, args):
         args = self.filter_substitution(args)
         self.do_include(args)
     def do_error(self, args):
         raise Preprocessor.Error(self, 'Error: ', str(args))
 
--- a/python/mozbuild/mozbuild/test/test_preprocessor.py
+++ b/python/mozbuild/mozbuild/test/test_preprocessor.py
@@ -551,16 +551,66 @@ class TestPreprocessor(unittest.TestCase
                 '',
             ]),
         }
 
         with MockedOpen(files):
             self.pp.do_include('f')
             self.assertEqual(self.pp.out.getvalue(), 'foobarbaz\nbarfoobaz\n')
 
+    def test_include_line(self):
+        files = {
+            'test.js': '\n'.join([
+                '#define foo foobarbaz',
+                '#include @inc@',
+                '@bar@',
+                '',
+            ]),
+            'bar.js': '\n'.join([
+                '#define bar barfoobaz',
+                '@foo@',
+                '',
+            ]),
+            'foo.js': '\n'.join([
+                'bazfoobar',
+                '#include bar.js',
+                'bazbarfoo',
+                '',
+            ]),
+            'baz.js': 'baz\n',
+            'f.js': '\n'.join([
+                '#include foo.js',
+                '#filter substitution',
+                '#define inc bar.js',
+                '#include test.js',
+                '#include baz.js',
+                'fin',
+                '',
+            ]),
+        }
+
+        with MockedOpen(files):
+            self.pp.do_include('f.js')
+            self.assertEqual(self.pp.out.getvalue(),
+                             ('//@line 1 "CWD/foo.js"\n'
+                              'bazfoobar\n'
+                              '//@line 2 "CWD/bar.js"\n'
+                              '@foo@\n'
+                              '//@line 3 "CWD/foo.js"\n'
+                              'bazbarfoo\n'
+                              '//@line 2 "CWD/bar.js"\n'
+                              'foobarbaz\n'
+                              '//@line 3 "CWD/test.js"\n'
+                              'barfoobaz\n'
+                              '//@line 1 "CWD/baz.js"\n'
+                              'baz\n'
+                              '//@line 6 "CWD/f.js"\n'
+                              'fin\n').replace('CWD/',
+                                               os.getcwd() + os.path.sep))
+
     def test_include_missing_file(self):
         with MockedOpen({'f': '#include foo\n'}):
             with self.assertRaises(Preprocessor.Error) as e:
                 self.pp.do_include('f')
                 self.assertEqual(e.key, 'FILE_NOT_FOUND')
 
     def test_include_undefined_variable(self):
         with MockedOpen({'f': '#filter substitution\n#include @foo@\n'}):