{"action":"created","comment":{"url":"https://api.github.com/repos/jscs-dev/node-jscs/pulls/comments/24472591","id":24472591,"diff_hunk":"@@ -0,0 +1,216 @@\n+var htmlparser = require('htmlparser2');\n+var Errors = require('./errors');\n+\n+/**\n+ * Html file representation (needs for errors output).\n+ *\n+ * @name HtmlFile\n+ */\n+var HtmlFile = function(filename, source) {\n+    this._filename = filename;\n+    this._lines = source.split(/\\r\\n|\\r|\\n/);\n+};\n+\n+HtmlFile.prototype = {\n+    /**\n+     * Returns source filename for this object representation.\n+     *\n+     * @returns {String}\n+     */\n+    getFilename: function() {\n+        return this._filename;\n+    },\n+    /**\n+     * Returns array of source lines for the file.\n+     *\n+     * @returns {String[]}\n+     */\n+    getLines: function() {\n+        return this._lines;\n+    }\n+};\n+\n+/**\n+ * Parse html and retrieve script sources.\n+ * @param {String} html\n+ * @returns {Object[]}\n+ */\n+function getScripts(html) {\n+    function onopen(name, attrs) {\n+        // tag should be a <script>\n+        if (name !== 'script') {\n+            return;\n+        }\n+\n+        // ignore scripts with src attribute\n+        if (attrs.src) {\n+            return;\n+        }\n+\n+        // script tag should has no type attribute or attribute should be equal to text/javascript\n+        if (attrs.type && attrs.type.toLowerCase() !== 'text/javascript') {\n+            return;\n+        }\n+\n+        // store script content start pos\n+        scriptStartPos = parser.endIndex + 1;\n+    }\n+\n+    function onclose() {\n+        // if there is a script start pos\n+        if (scriptStartPos) {\n+            // get script content\n+            var scriptEndPos = parser.startIndex;\n+            var source = html.substring(scriptStartPos, scriptEndPos);\n+\n+            // store script content only if it contains non-whitespace characters\n+            if (/\\S/.test(source)) {\n+                scripts.push({\n+                    source: source,\n+                    start: scriptStartPos,\n+                    end: scriptEndPos\n+                });\n+            }\n+\n+            // reset script start position\n+            scriptStartPos = 0;\n+        }\n+    }\n+\n+    var scriptStartPos = 0;\n+    var scripts = [];\n+    var parser = new htmlparser.Parser({\n+        onopentag: onopen,\n+        onclosetag: onclose\n+    });\n+\n+    parser.parseComplete(html);\n+\n+    return scripts;\n+}\n+\n+/**\n+ * Trim script source and normalize lines offset.\n+ * @param {String} source\n+ * @returns {Object[]}\n+ */\n+function normalizeSource(source) {\n+    var lines = source.split(/\\r\\n?|\\n\\r?/);\n+    var lineCount = lines.length;\n+    var lastLine = lines.pop();\n+    var tabOnlyOffset = false;\n+    var spaceOnlyOffset = false;\n+    var offset;\n+\n+    // remove first list if it's an empty string\n+    // usualy <script> starts with new line\n+    if (!lines[0]) {\n+        lines.shift();\n+    }\n+\n+    // replace last line by empty string if it contains only whitespaces\n+    // it helps avoid disallowTrailingWhitespace errors on last line\n+    lines.push(/\\S/.test(lastLine) ? lastLine : '');\n+\n+    // calculate min line offset\n+    offset = Math.min.apply(null, lines.map(function(line) {\n+        // skip empty lines\n+        if (!line) {\n+            return Infinity;\n+        }\n+\n+        // fetch whitespaces at the line begining\n+        var offsetStr = line.match(/^\\s*/)[0];\n+        var tabCount = offsetStr.match(/\\t*/)[0].length;\n+\n+        // mixed spaces and tabs in one offset -> don't remove offsets\n+        if (tabCount && tabCount !== offsetStr.length) {\n+            return 0;\n+        }\n+\n+        if (tabCount) {\n+            if (spaceOnlyOffset) {\n+                // no spaces, but previous offset has ony spaces -> mixed spaces and tabs\n+                return 0;\n+            } else {\n+                // remember offset contains only tabs\n+                tabOnlyOffset = true;\n+            }\n+        } else {\n+            if (tabOnlyOffset) {\n+                // no tabs, but previous offset has only tabs -> mixed spaces and tabs\n+                return 0;\n+            } else {\n+                // remember offset contains only spaces\n+                spaceOnlyOffset = true;\n+            }\n+        }\n+\n+        return offsetStr.length;\n+    }));\n+\n+    // remove common offsets if possible\n+    if (offset) {\n+        lines = lines.map(function(line) {\n+            return line.substr(offset);\n+        });\n+    }\n+\n+    return {\n+        source: lines.join('\\n'),\n+        offset: offset,\n+        lineCount: lineCount\n+    };\n+}\n+\n+/**\n+ * Parse HTML and search for <script> sources. Each script source also normalize\n+ * by line offset. Result contains script sources with information about line\n+ * offset (that was removed for each line) and lines count before script source.\n+ * This information helps restore absolute positions in html file for errors.\n+ * @param {String}\n+ * @returns {Object[]}\n+ */\n+function extractJs(filename, data) {\n+    var errors = new Errors(new HtmlFile(filename, data));\n+    var scripts = getScripts(data);\n+    var sources = [];\n+    var line = 1;\n+    var lastHtmlPos = 0;\n+\n+    for (var i = 0; i < scripts.length; i++) {\n+        // fetch script source and normalize it\n+        var scriptInfo = scripts[i];\n+        var normalized = normalizeSource(scriptInfo.source);\n+\n+        // add line offset before script\n+        line += data.substring(lastHtmlPos, scriptInfo.start).split(/\\r\\n?|\\n\\r?/).length - 1;\n+\n+        // store result\n+        sources.push({\n+            source: normalized.source,\n+            offset: normalized.offset,\n+            line: line\n+        });\n+\n+        // save offsets for next fragment\n+        line += normalized.lineCount - 1;\n+        lastHtmlPos = scriptInfo.end;\n+    }\n+\n+    return {\n+        sources: sources,\n+        errors: errors,\n+        addError: function(error) {\n+            errors._errorList.push({","path":"lib/extract-js.js","position":205,"original_position":205,"commit_id":"cad51eb56c27ad38bfbd7b32b8c373ac987a4bc1","original_commit_id":"cad51eb56c27ad38bfbd7b32b8c373ac987a4bc1","user":{"login":"mrjoelkemp","id":865203,"avatar_url":"https://avatars.githubusercontent.com/u/865203?v=3","gravatar_id":"","url":"https://api.github.com/users/mrjoelkemp","html_url":"https://github.com/mrjoelkemp","followers_url":"https://api.github.com/users/mrjoelkemp/followers","following_url":"https://api.github.com/users/mrjoelkemp/following{/other_user}","gists_url":"https://api.github.com/users/mrjoelkemp/gists{/gist_id}","starred_url":"https://api.github.com/users/mrjoelkemp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mrjoelkemp/subscriptions","organizations_url":"https://api.github.com/users/mrjoelkemp/orgs","repos_url":"https://api.github.com/users/mrjoelkemp/repos","events_url":"https://api.github.com/users/mrjoelkemp/events{/privacy}","received_events_url":"https://api.github.com/users/mrjoelkemp/received_events","type":"User","site_admin":false},"body":"Consider:\r\n\r\n```js\r\naddError: function(error) {\r\n    error.filename = filename;\r\n    errors.add(error);\r\n}\r\n```","created_at":"2015-02-11T03:33:33Z","updated_at":"2015-02-11T03:33:33Z","html_url":"https://github.com/jscs-dev/node-jscs/pull/1038#discussion_r24472591","pull_request_url":"https://api.github.com/repos/jscs-dev/node-jscs/pulls/1038","_links":{"self":{"href":"https://api.github.com/repos/jscs-dev/node-jscs/pulls/comments/24472591"},"html":{"href":"https://github.com/jscs-dev/node-jscs/pull/1038#discussion_r24472591"},"pull_request":{"href":"https://api.github.com/repos/jscs-dev/node-jscs/pulls/1038"}}},"pull_request":{"url":"https://api.github.com/repos/jscs-dev/node-jscs/pulls/1038","id":28972036,"html_url":"https://github.com/jscs-dev/node-jscs/pull/1038","diff_url":"https://github.com/jscs-dev/node-jscs/pull/1038.diff","patch_url":"https://github.com/jscs-dev/node-jscs/pull/1038.patch","issue_url":"https://api.github.com/repos/jscs-dev/node-jscs/issues/1038","number":1038,"state":"open","locked":false,"title":"Extract JavaScript to validate from HTML files (fixes #764)","user":{"login":"lahmatiy","id":270491,"avatar_url":"https://avatars.githubusercontent.com/u/270491?v=3","gravatar_id":"","url":"https://api.github.com/users/lahmatiy","html_url":"https://github.com/lahmatiy","followers_url":"https://api.github.com/users/lahmatiy/followers","following_url":"https://api.github.com/users/lahmatiy/following{/other_user}","gists_url":"https://api.github.com/users/lahmatiy/gists{/gist_id}","starred_url":"https://api.github.com/users/lahmatiy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lahmatiy/subscriptions","organizations_url":"https://api.github.com/users/lahmatiy/orgs","repos_url":"https://api.github.com/users/lahmatiy/repos","events_url":"https://api.github.com/users/lahmatiy/events{/privacy}","received_events_url":"https://api.github.com/users/lahmatiy/received_events","type":"User","site_admin":false},"body":"Implementation of `extract` option, that behave similar to jshint `extract` option (see #764 for details).\r\nSolution tries avoid undesirable errors by `disallowTrailingWhitespace`, `disallowMixedSpacesAndTabs ` and `validateIndentation` rules.","created_at":"2015-02-10T00:58:29Z","updated_at":"2015-02-11T03:33:33Z","closed_at":null,"merged_at":null,"merge_commit_sha":"038f02c902bb9877514c1ba717c7cbf537a89218","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/jscs-dev/node-jscs/pulls/1038/commits","review_comments_url":"https://api.github.com/repos/jscs-dev/node-jscs/pulls/1038/comments","review_comment_url":"https://api.github.com/repos/jscs-dev/node-jscs/pulls/comments/{number}","comments_url":"https://api.github.com/repos/jscs-dev/node-jscs/issues/1038/comments","statuses_url":"https://api.github.com/repos/jscs-dev/node-jscs/statuses/cad51eb56c27ad38bfbd7b32b8c373ac987a4bc1","head":{"label":"lahmatiy:extract","ref":"extract","sha":"cad51eb56c27ad38bfbd7b32b8c373ac987a4bc1","user":{"login":"lahmatiy","id":270491,"avatar_url":"https://avatars.githubusercontent.com/u/270491?v=3","gravatar_id":"","url":"https://api.github.com/users/lahmatiy","html_url":"https://github.com/lahmatiy","followers_url":"https://api.github.com/users/lahmatiy/followers","following_url":"https://api.github.com/users/lahmatiy/following{/other_user}","gists_url":"https://api.github.com/users/lahmatiy/gists{/gist_id}","starred_url":"https://api.github.com/users/lahmatiy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lahmatiy/subscriptions","organizations_url":"https://api.github.com/users/lahmatiy/orgs","repos_url":"https://api.github.com/users/lahmatiy/repos","events_url":"https://api.github.com/users/lahmatiy/events{/privacy}","received_events_url":"https://api.github.com/users/lahmatiy/received_events","type":"User","site_admin":false},"repo":{"id":15948082,"name":"node-jscs","full_name":"lahmatiy/node-jscs","owner":{"login":"lahmatiy","id":270491,"avatar_url":"https://avatars.githubusercontent.com/u/270491?v=3","gravatar_id":"","url":"https://api.github.com/users/lahmatiy","html_url":"https://github.com/lahmatiy","followers_url":"https://api.github.com/users/lahmatiy/followers","following_url":"https://api.github.com/users/lahmatiy/following{/other_user}","gists_url":"https://api.github.com/users/lahmatiy/gists{/gist_id}","starred_url":"https://api.github.com/users/lahmatiy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lahmatiy/subscriptions","organizations_url":"https://api.github.com/users/lahmatiy/orgs","repos_url":"https://api.github.com/users/lahmatiy/repos","events_url":"https://api.github.com/users/lahmatiy/events{/privacy}","received_events_url":"https://api.github.com/users/lahmatiy/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/lahmatiy/node-jscs","description":"JavaScript Code Style checker","fork":true,"url":"https://api.github.com/repos/lahmatiy/node-jscs","forks_url":"https://api.github.com/repos/lahmatiy/node-jscs/forks","keys_url":"https://api.github.com/repos/lahmatiy/node-jscs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/lahmatiy/node-jscs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/lahmatiy/node-jscs/teams","hooks_url":"https://api.github.com/repos/lahmatiy/node-jscs/hooks","issue_events_url":"https://api.github.com/repos/lahmatiy/node-jscs/issues/events{/number}","events_url":"https://api.github.com/repos/lahmatiy/node-jscs/events","assignees_url":"https://api.github.com/repos/lahmatiy/node-jscs/assignees{/user}","branches_url":"https://api.github.com/repos/lahmatiy/node-jscs/branches{/branch}","tags_url":"https://api.github.com/repos/lahmatiy/node-jscs/tags","blobs_url":"https://api.github.com/repos/lahmatiy/node-jscs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/lahmatiy/node-jscs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/lahmatiy/node-jscs/git/refs{/sha}","trees_url":"https://api.github.com/repos/lahmatiy/node-jscs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/lahmatiy/node-jscs/statuses/{sha}","languages_url":"https://api.github.com/repos/lahmatiy/node-jscs/languages","stargazers_url":"https://api.github.com/repos/lahmatiy/node-jscs/stargazers","contributors_url":"https://api.github.com/repos/lahmatiy/node-jscs/contributors","subscribers_url":"https://api.github.com/repos/lahmatiy/node-jscs/subscribers","subscription_url":"https://api.github.com/repos/lahmatiy/node-jscs/subscription","commits_url":"https://api.github.com/repos/lahmatiy/node-jscs/commits{/sha}","git_commits_url":"https://api.github.com/repos/lahmatiy/node-jscs/git/commits{/sha}","comments_url":"https://api.github.com/repos/lahmatiy/node-jscs/comments{/number}","issue_comment_url":"https://api.github.com/repos/lahmatiy/node-jscs/issues/comments/{number}","contents_url":"https://api.github.com/repos/lahmatiy/node-jscs/contents/{+path}","compare_url":"https://api.github.com/repos/lahmatiy/node-jscs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/lahmatiy/node-jscs/merges","archive_url":"https://api.github.com/repos/lahmatiy/node-jscs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/lahmatiy/node-jscs/downloads","issues_url":"https://api.github.com/repos/lahmatiy/node-jscs/issues{/number}","pulls_url":"https://api.github.com/repos/lahmatiy/node-jscs/pulls{/number}","milestones_url":"https://api.github.com/repos/lahmatiy/node-jscs/milestones{/number}","notifications_url":"https://api.github.com/repos/lahmatiy/node-jscs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/lahmatiy/node-jscs/labels{/name}","releases_url":"https://api.github.com/repos/lahmatiy/node-jscs/releases{/id}","created_at":"2014-01-15T20:56:13Z","updated_at":"2014-12-16T12:28:35Z","pushed_at":"2015-02-10T11:22:43Z","git_url":"git://github.com/lahmatiy/node-jscs.git","ssh_url":"git@github.com:lahmatiy/node-jscs.git","clone_url":"https://github.com/lahmatiy/node-jscs.git","svn_url":"https://github.com/lahmatiy/node-jscs","homepage":"","size":2428,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"jscs-dev:master","ref":"master","sha":"a5bd1f3d8c5c30ecec501b1b40c1b784a77584ba","user":{"login":"jscs-dev","id":8018201,"avatar_url":"https://avatars.githubusercontent.com/u/8018201?v=3","gravatar_id":"","url":"https://api.github.com/users/jscs-dev","html_url":"https://github.com/jscs-dev","followers_url":"https://api.github.com/users/jscs-dev/followers","following_url":"https://api.github.com/users/jscs-dev/following{/other_user}","gists_url":"https://api.github.com/users/jscs-dev/gists{/gist_id}","starred_url":"https://api.github.com/users/jscs-dev/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jscs-dev/subscriptions","organizations_url":"https://api.github.com/users/jscs-dev/orgs","repos_url":"https://api.github.com/users/jscs-dev/repos","events_url":"https://api.github.com/users/jscs-dev/events{/privacy}","received_events_url":"https://api.github.com/users/jscs-dev/received_events","type":"Organization","site_admin":false},"repo":{"id":10697582,"name":"node-jscs","full_name":"jscs-dev/node-jscs","owner":{"login":"jscs-dev","id":8018201,"avatar_url":"https://avatars.githubusercontent.com/u/8018201?v=3","gravatar_id":"","url":"https://api.github.com/users/jscs-dev","html_url":"https://github.com/jscs-dev","followers_url":"https://api.github.com/users/jscs-dev/followers","following_url":"https://api.github.com/users/jscs-dev/following{/other_user}","gists_url":"https://api.github.com/users/jscs-dev/gists{/gist_id}","starred_url":"https://api.github.com/users/jscs-dev/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jscs-dev/subscriptions","organizations_url":"https://api.github.com/users/jscs-dev/orgs","repos_url":"https://api.github.com/users/jscs-dev/repos","events_url":"https://api.github.com/users/jscs-dev/events{/privacy}","received_events_url":"https://api.github.com/users/jscs-dev/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/jscs-dev/node-jscs","description":"JavaScript Code Style checker","fork":false,"url":"https://api.github.com/repos/jscs-dev/node-jscs","forks_url":"https://api.github.com/repos/jscs-dev/node-jscs/forks","keys_url":"https://api.github.com/repos/jscs-dev/node-jscs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jscs-dev/node-jscs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jscs-dev/node-jscs/teams","hooks_url":"https://api.github.com/repos/jscs-dev/node-jscs/hooks","issue_events_url":"https://api.github.com/repos/jscs-dev/node-jscs/issues/events{/number}","events_url":"https://api.github.com/repos/jscs-dev/node-jscs/events","assignees_url":"https://api.github.com/repos/jscs-dev/node-jscs/assignees{/user}","branches_url":"https://api.github.com/repos/jscs-dev/node-jscs/branches{/branch}","tags_url":"https://api.github.com/repos/jscs-dev/node-jscs/tags","blobs_url":"https://api.github.com/repos/jscs-dev/node-jscs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jscs-dev/node-jscs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jscs-dev/node-jscs/git/refs{/sha}","trees_url":"https://api.github.com/repos/jscs-dev/node-jscs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jscs-dev/node-jscs/statuses/{sha}","languages_url":"https://api.github.com/repos/jscs-dev/node-jscs/languages","stargazers_url":"https://api.github.com/repos/jscs-dev/node-jscs/stargazers","contributors_url":"https://api.github.com/repos/jscs-dev/node-jscs/contributors","subscribers_url":"https://api.github.com/repos/jscs-dev/node-jscs/subscribers","subscription_url":"https://api.github.com/repos/jscs-dev/node-jscs/subscription","commits_url":"https://api.github.com/repos/jscs-dev/node-jscs/commits{/sha}","git_commits_url":"https://api.github.com/repos/jscs-dev/node-jscs/git/commits{/sha}","comments_url":"https://api.github.com/repos/jscs-dev/node-jscs/comments{/number}","issue_comment_url":"https://api.github.com/repos/jscs-dev/node-jscs/issues/comments/{number}","contents_url":"https://api.github.com/repos/jscs-dev/node-jscs/contents/{+path}","compare_url":"https://api.github.com/repos/jscs-dev/node-jscs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jscs-dev/node-jscs/merges","archive_url":"https://api.github.com/repos/jscs-dev/node-jscs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jscs-dev/node-jscs/downloads","issues_url":"https://api.github.com/repos/jscs-dev/node-jscs/issues{/number}","pulls_url":"https://api.github.com/repos/jscs-dev/node-jscs/pulls{/number}","milestones_url":"https://api.github.com/repos/jscs-dev/node-jscs/milestones{/number}","notifications_url":"https://api.github.com/repos/jscs-dev/node-jscs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jscs-dev/node-jscs/labels{/name}","releases_url":"https://api.github.com/repos/jscs-dev/node-jscs/releases{/id}","created_at":"2013-06-14T20:53:48Z","updated_at":"2015-02-10T17:15:50Z","pushed_at":"2015-02-11T01:03:06Z","git_url":"git://github.com/jscs-dev/node-jscs.git","ssh_url":"git@github.com:jscs-dev/node-jscs.git","clone_url":"https://github.com/jscs-dev/node-jscs.git","svn_url":"https://github.com/jscs-dev/node-jscs","homepage":"http://jscs.info/","size":8841,"stargazers_count":2492,"watchers_count":2492,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":249,"mirror_url":null,"open_issues_count":109,"forks":249,"open_issues":109,"watchers":2492,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/jscs-dev/node-jscs/pulls/1038"},"html":{"href":"https://github.com/jscs-dev/node-jscs/pull/1038"},"issue":{"href":"https://api.github.com/repos/jscs-dev/node-jscs/issues/1038"},"comments":{"href":"https://api.github.com/repos/jscs-dev/node-jscs/issues/1038/comments"},"review_comments":{"href":"https://api.github.com/repos/jscs-dev/node-jscs/pulls/1038/comments"},"review_comment":{"href":"https://api.github.com/repos/jscs-dev/node-jscs/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/jscs-dev/node-jscs/pulls/1038/commits"},"statuses":{"href":"https://api.github.com/repos/jscs-dev/node-jscs/statuses/cad51eb56c27ad38bfbd7b32b8c373ac987a4bc1"}}}}