Permalink
Browse files

wip

  • Loading branch information...
1 parent 214570b commit 5dd21d8f1d8805f98d58daa1966cfb395fa456f2 @juzna committed Feb 15, 2014
@@ -51,6 +51,41 @@ public function render(\Exception $exception)
/**
+ * Get enhanced stack trace for an exception
+ * @param \Exception
+ * @return array
+ */
+ private function getStack(\Exception $ex)
+ {
+ $stack = $ex->getTrace();
+
+ foreach ($stack as &$row) {
+ if (isset($row['file']) && !isset($row['line']) && is_file($row['file'])) {
+ $this->remapStackLine($row['file'], $row['line']);
+ }
+ }
+ return $stack;
+ }
+
+
+
+ /**
+ * Find original source, see Helpers::sourceMapLookup()
+ * @param string
+ * @param int
+ * @return void
+ */
+ private function remapStackLine(&$originalFile, &$originalLine)
+ {
+ $ret = SourceMapHelper::sourceMapLookup($originalFile, $originalLine);
+ if ($ret) {
+ list ($originalFile, $originalLine) = $ret;
+ }
+ }
+
+
+
+ /**
* Returns syntax highlighted source code.
* @param string
* @param int
@@ -0,0 +1,47 @@
+<?php
+
+namespace Nette\Diagnostics;
+
+
+/**
+ * Resolves source maps
+ */
+class SourceMapHelper
+{
+
+ /**
+ * Converts line number in compiled file into line number in source file (e.g. for latte templates)
+ *
+ * @param string
+ * @param int
+ * @return null|array [ sourceFile, sourceLine ] or null if not found
+ */
+ public static function sourceMapLookup($originalFile, $originalLine)
+ {
+ $data = file_get_contents($originalFile);
+ if (!preg_match('~^// source file: (.+)$~m', $data, $match)) {
+ return;
+ }
+ $sourceFile = trim($match[1]);
+
+ if (!preg_match('~// source map: ([^\s]+)~', $data, $match)) {
+ return;
+ }
+ $map = json_decode($match[1]);
+
+ $compiledLine = $originalLine - substr_count($data, "\n", 0, strpos($data, $match[0])) - 1; // adjust line for source map beginning (some lines before the mapped file + 1 line of source map definition)
+
+ // find closest key
+ $compiledLines = array();
+ foreach (array_keys((array) $map) as $line) {
+ $compiledLines[$line] = abs($line - $compiledLine);
+ }
+ asort($compiledLines);
+ $closestLine = key($compiledLines);
+
+ return array($sourceFile, reset($map->$closestLine));
+ }
+
+}
+
+
@@ -57,7 +57,7 @@ $counter = 0;
<?php $ex = $exception; $level = 0; ?>
- <?php do { ?>
+ <?php do { $exFile = $ex->getFile(); $exLine = $ex->getLine(); $this->remapStackLine($exFile, $exLine); ?>
<?php if ($level++): ?>
<div class="panel"<?php if ($level === 2) echo ' id="netteCaused"' ?>>
@@ -84,8 +84,8 @@ $counter = 0;
<?php endforeach ?>
- <?php $stack = $ex->getTrace(); $expanded = NULL ?>
- <?php if ((!$exception instanceof \ErrorException || in_array($exception->getSeverity(), array(E_USER_NOTICE, E_USER_WARNING, E_USER_DEPRECATED))) && $this->isCollapsed($ex->getFile())) {
+ <?php $stack = $this->getStack($ex); $expanded = NULL ?>
+ <?php if ((!$exception instanceof \ErrorException || in_array($exception->getSeverity(), array(E_USER_NOTICE, E_USER_WARNING, E_USER_DEPRECATED))) && $this->isCollapsed($exFile)) {
foreach ($stack as $key => $row) {
if (isset($row['file']) && !$this->isCollapsed($row['file'])) { $expanded = $key; break; }
}
@@ -95,8 +95,8 @@ $counter = 0;
<h2><a href="#netteBsPnl<?php echo ++$counter ?>" class="nette-toggle<?php echo ($collapsed = $expanded !== NULL) ? '-collapsed' : '' ?>">Source file</a></h2>
<div id="netteBsPnl<?php echo $counter ?>" class="<?php echo $collapsed ? 'nette-collapsed ' : '' ?>inner">
- <p><b>File:</b> <?php echo Helpers::editorLink($ex->getFile(), $ex->getLine()) ?></p>
- <?php if (is_file($ex->getFile())): ?><?php echo self::highlightFile($ex->getFile(), $ex->getLine(), 15, $ex instanceof \ErrorException && isset($ex->context) ? $ex->context : NULL) ?><?php endif ?>
+ <p><b>File:</b> <?php echo Helpers::editorLink($exFile, $exLine) ?></p>
+ <?php if (is_file($exFile)): ?><?php echo self::highlightFile($exFile, $exLine, 15, $ex instanceof \ErrorException && isset($ex->context) ? $ex->context : NULL) ?><?php endif ?>
</div></div>
@@ -108,6 +108,7 @@ $counter = 0;
<div id="netteBsPnl<?php echo $counter ?>" class="inner">
<ol>
<?php foreach ($stack as $key => $row): ?>
+ <?php if (isset($row['file'], $row['line'])) $this->remapStackLine($row['file'], $row['line']); ?>
<li><p>
<?php if (isset($row['file']) && is_file($row['file'])): ?>
View
@@ -18,6 +18,9 @@
*/
class Compiler extends Nette\Object
{
+ /** @var bool should source map be created? Causes hints to be generated into code */
+ public static $createSourceMap = TRUE;
+
/** @var string default content type */
public $defaultContentType = self::CONTENT_HTML;
@@ -138,6 +141,17 @@ public function compile(array $tokens)
}
$output = $this->expandTokens($output);
+
+ // attach source map
+ if (static::$createSourceMap) {
+ $sourceMap = $this->extractSourceMap($output);
+ $hash = sha1($output);
+ $output = "<?php // source map $hash: " . json_encode($sourceMap) . " ?>\n"
+ . $output
+ . "\n<?php // end of source map $hash ?>";
+ ;
+ }
+
return $output;
}
@@ -242,6 +256,30 @@ private function processMacroTag(Token $token)
}
+ /**
+ * Extract source map from generated code with hints
+ * @param string
+ * @return array { compiledLine => [ sourceLine ] } which maps compiled code back to its source
+ */
+ private function extractSourceMap(&$output)
+ {
+ $map = array();
+ $line = 1;
+ $output = \Nette\Utils\Strings::replace($output, '~([^\x03-\x05]+)|\x03([^\x04]+)\x04([^\x05]*)\x05~m', function($match) use(&$map, &$line) {
+ if (!isset($match[2])) {
+ $ret = $match[1];
+ } else {
+ $map[intval($line)][] = intval($match[2]);
+ $ret = $match[3];
+ }
+ $line += substr_count($match[0], "\n");
+ return $ret;
+ });
+ return array_map('array_unique', $map);
+ }
+
+
+
private function processHtmlTagBegin(Token $token)
{
if ($token->closing) {
@@ -441,7 +479,11 @@ private function writeCode($code, & $output, $isRightmost, $isLeftmost = NULL)
$code .= "\n"; // double newline to avoid newline eating by PHP
}
}
- $output .= $code;
+ if (static::$createSourceMap && !preg_match('~<\?php\s+\?>~', $code)) {
+ $output .= "\x03{$this->tokens[$this->position]->line}\x04$code\x05";
+ } else {
+ $output .= $code;
+ }
}
@@ -516,7 +558,7 @@ public function writeAttrsMacro($code)
}
}
- if ($right && substr($this->output, -2) === '?>') {
+ if ($right && (self::$createSourceMap ? substr($this->output, -3, 2) : substr($this->output, -2)) === '?>') {
$this->output .= "\n";
}
}
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * Test: Nette\Diagnostics\Helper::sourceMapLookup()
+ *
+ * @author Jan Dolecek
+ * @package Nette\Diagnostics
+ * @subpackage UnitTests
+ */
+
+use Nette\Diagnostics\SourceMapHelper;
+use Tester\Assert;
+
+
+
+require __DIR__ . '/../bootstrap.php';
+
+
+$file = __DIR__ . '/SourceMap.phtml';
+Assert::same( array('SourceMap.phpt', 1), SourceMapHelper::sourceMapLookup($file, 15) );
+Assert::same( array('SourceMap.phpt', 15), SourceMapHelper::sourceMapLookup($file, 16) );
+Assert::same( array('SourceMap.phpt', 15), SourceMapHelper::sourceMapLookup($file, 17) );
+Assert::same( NULL, SourceMapHelper::sourceMapLookup($file, 19) );
+
+// Another source map within the same file - not supported at the moment
+// Assert::same( array('SourceMap.phpt', 1), Helpers::sourceMapLookup($file, 32) );
@@ -0,0 +1,34 @@
+<?php
+// Example of compiled file with source-map
+$sourceMap = array(
+ array(4, 1), // source line 1 compiled to line 4
+ array(5, 15),
+ array(6, 15),
+);
+echo serialize($sourceMap);
+
+// source file: SourceMap.phpt
+//sourceMap[073e15abd403c7c814443453dce450affde014a8]000086:a:3:{i:0;a:2:{i:0;i:4;i:1;i:1;}i:1;a:2:{i:0;i:5;i:1;i:15;}i:2;a:2:{i:0;i:6;i:1;i:15;}} -- after this the payload starts
+$line1 =
+ NULL; // of the original file
+
+$compiledVariable1 = 1; // from original line 1
+if ($x && $y) $compiledVariable15 = 2;
+else $compiledVariable15 = 'ABCD';
+
+$notFromSource = 'X';
+
+//eof:sourceMap[073e15abd403c7c814443453dce450affde014a8]
+
+
+$glueCode = 1; // not from source
+
+
+// Another source map within the same file
+//sourceMap[0000000000000000000000000000000000000011]000032:a:1:{i:0;a:2:{i:0;i:4;i:1;i:1;}} -- after this the payload starts
+$line1 =
+ NULL; // of the original file
+
+$compiledVariable1 = 1;
+
+//eof:sourceMap[0000000000000000000000000000000000000011]
@@ -33,5 +33,6 @@ function codefix($s)
{
$s = preg_replace("#(?<=['_])[a-z0-9]{10,}(?=['_])#", 'xxx', $s);
$s = preg_replace('#source file:.*#', '', $s);
+ $s = preg_replace('#\s*((<\\?php )?//eof:|//)sourceMap\\[.{40}\\].*#', '', $s);
return $s;
}
@@ -0,0 +1,98 @@
+<?php // source map %a%: {"21":[14],"25":[24],"26":[18],"27":[21],"32":[24],"35":[31],"36":[28],"37":[29],"38":[30],"40":[31],"50":[12]}
+
+
+// prolog Nette\Latte\Macros\CoreMacros
+list($_l, $_g) = Nette\Latte\Macros\CoreMacros::initRuntime($template, '%a%')
+;
+// prolog Nette\Latte\Macros\UIMacros
+//
+// block css
+//
+if (!function_exists($_l->blocks['css'][] = '%a%_css')) { function %a%_css($_l, $_args) { foreach ($_args as $__k => $__v) $$__k = $__v
+?> .no-images img { display: none; }
+ .x .y { font-weight: bold; }
+<?php
+}}
+
+//
+// block content
+//
+if (!function_exists($_l->blocks['content'][] = '%a%_content')) { function %a%_content($_l, $_args) { foreach ($_args as $__k => $__v) $$__k = $__v
+; call_user_func(reset($_l->blocks['title']), $_l, get_defined_vars()) {* original-line 12 *}
+
+;
+
+ echo Nette\Templating\Helpers::escapeHtml($x, ENT_NOQUOTES) {* original-line 14 *}
+?>
+
+
+<ul>
+<?php $iterations = 0; foreach ($items as $item) { ?> <li>
+ Item <?php echo Nette\Templating\Helpers::escapeHtml($item->id, ENT_NOQUOTES) ?>:
+ <a
+ onmouseover="console.log(this)" href="<?php echo htmlSpecialChars($_control->link("item", array($item->id))) ?>"<?php if ($_l->tmp = array_filter(array($item->superCool ? 'superCool' : NULL))) echo ' class="' . htmlSpecialChars(implode(" ", array_unique($_l->tmp))) . '"' ?>>
+ Link
+ </a>
+ </li>
+<?php $iterations++; }
+?>
+</ul>
+
+<?php Nette\Latte\Macros\FormMacros::renderFormBegin($form = $_form = $_control["myForm"], array())
+?>
+ Name: <?php echo $_form["name"]->getControl() ?><br>
+ Mail: <?php echo $_form["email"]->getControl() ?><br>
+ <?php echo $_form["add"]->getControl()
+?>
+
+<?php Nette\Latte\Macros\FormMacros::renderFormEnd($_form)
+?>
+
+<hr>
+<?php
+}}
+
+//
+// block title
+//
+if (!function_exists($_l->blocks['title'][] = '%a%_title')) { function %a%_title($_l, $_args) { foreach ($_args as $__k => $__v) $$__k = $__v
+?><h1>Page <?php echo Nette\Templating\Helpers::escapeHtml($name, ENT_NOQUOTES) ?></h1>
+<?php
+}}
+
+//
+// end of blocks
+//
+
+// template extending and snippets support
+
+$_l->extends = empty($template->_extended) && isset($_control) && $_control instanceof Nette\Application\UI\Presenter ? $_control->findLayoutTemplateFile() : NULL; $template->_extended = $_extended = TRUE;
+
+
+if ($_l->extends) {
+ ob_start();
+
+} elseif (!empty($_control->snippetMode)) {
+ return Nette\Latte\Macros\UIMacros::renderSnippets($_control, $_l, get_defined_vars());
+}
+
+//
+// main template
+//
+
+
+
+
+ if ($_l->extends) { ob_end_clean(); return Nette\Latte\Macros\CoreMacros::includeTemplate($_l->extends, get_defined_vars(), $template)->render(); }
+call_user_func(reset($_l->blocks['css']), $_l, get_defined_vars())
+
+
+?>
+
+
+
+<?php call_user_func(reset($_l->blocks['content']), $_l, get_defined_vars())
+
+;
+
+ // end of source map %a%
Oops, something went wrong.

0 comments on commit 5dd21d8

Please sign in to comment.