author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Thu, 17 Dec 2015 11:57:06 +0100 | |
changeset 276777 | f143af51f6e35932927b8ccac2509facbbe7b539 |
parent 276723 | 77ab820ce477438af4b32cea83f82fd55744cec7 (current diff) |
parent 276776 | 1afbb237019de001797d566cd33dd8fa5a9395d1 (diff) |
child 276778 | 0711218a018d912036f7d3be2ae2649e213cfb85 |
push id | 69273 |
push user | cbook@mozilla.com |
push date | Thu, 17 Dec 2015 11:03:52 +0000 |
treeherder | mozilla-inbound@565d7ae436ba [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 46.0a1 |
first release with | nightly linux32
f143af51f6e3
/
46.0a1
/
20151217030207
/
files
nightly linux64
f143af51f6e3
/
46.0a1
/
20151217030207
/
files
nightly mac
f143af51f6e3
/
46.0a1
/
20151217030207
/
files
nightly win32
f143af51f6e3
/
46.0a1
/
20151217030207
/
files
nightly win64
f143af51f6e3
/
46.0a1
/
20151217030207
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
46.0a1
/
20151217030207
/
pushlog to previous
nightly linux64
46.0a1
/
20151217030207
/
pushlog to previous
nightly mac
46.0a1
/
20151217030207
/
pushlog to previous
nightly win32
46.0a1
/
20151217030207
/
pushlog to previous
nightly win64
46.0a1
/
20151217030207
/
pushlog to previous
|
--- a/.eslintignore +++ b/.eslintignore @@ -190,16 +190,17 @@ toolkit/modules/tests/xpcshell/test_task # Not yet updated toolkit/components/osfile/** toolkit/components/passwordmgr/** toolkit/components/places/** # Uses preprocessing toolkit/content/contentAreaUtils.js +toolkit/content/widgets/videocontrols.xml toolkit/components/jsdownloads/src/DownloadIntegration.jsm toolkit/components/search/nsSearchService.js toolkit/components/url-classifier/** toolkit/components/urlformatter/nsURLFormatter.js toolkit/identity/FirefoxAccounts.jsm toolkit/modules/AppConstants.jsm toolkit/modules/SessionRecorder.jsm toolkit/mozapps/downloads/nsHelperAppDlg.js
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1318,18 +1318,16 @@ sticky_pref("lightweightThemes.selectedT #endif // Whether the character encoding menu is under the main Firefox button. This // preference is a string so that localizers can alter it. pref("browser.menu.showCharacterEncoding", "chrome://browser/locale/browser.properties"); // Allow using tab-modal prompts when possible. pref("prompts.tab_modal.enabled", true); -// Whether the Panorama should animate going in/out of tabs -pref("browser.panorama.animate_zoom", true); // Activates preloading of the new tab url. pref("browser.newtab.preload", true); // Remembers if the about:newtab intro has been shown pref("browser.newtabpage.introShown", false); // Toggles the content of 'about:newtab'. Shows the grid when enabled.
--- a/browser/base/content/browser-menubar.inc +++ b/browser/base/content/browser-menubar.inc @@ -499,21 +499,16 @@ accesskey="&addons.accesskey;" key="key_openAddons" command="Tools:Addons"/> <menuitem id="menu_openApps" label="&webapps.label;" accesskey="&webapps.accesskey;" oncommand="BrowserOpenApps();"/> - <menuitem id="menu_openLoop" - label="&loopMenuItem.label;" - accesskey = "&loopMenuItem.accesskey;" - oncommand="LoopUI.togglePanel();"/> - <!-- only one of sync-setup, sync-syncnowitem or sync-reauthitem will be showing at once --> <menuitem id="sync-setup" label="&syncSignIn.label;" accesskey="&syncSignIn.accesskey;" observes="sync-setup-state" oncommand="gSyncUI.openSetup(null, 'menubar')"/> <menuitem id="sync-syncnowitem" label="&syncSyncNowItem.label;"
--- a/browser/base/content/tab-content.js +++ b/browser/base/content/tab-content.js @@ -329,17 +329,17 @@ var AboutReaderListener = { /** * NB: this function will update the state of the reader button asynchronously * after the next mozAfterPaint call (assuming reader mode is enabled and * this is a suitable document). Calling it on things which won't be * painted is not going to work. */ updateReaderButton: function(forceNonArticle) { if (!ReaderMode.isEnabledForParseOnLoad || this.isAboutReader || - !(content.document instanceof content.HTMLDocument) || + !content || !(content.document instanceof content.HTMLDocument) || content.document.mozSyntheticDocument) { return; } this.scheduleReadabilityCheckPostPaint(forceNonArticle); }, cancelPotentialPendingReadabilityCheck: function() {
--- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -649,17 +649,19 @@ // If the browser is loading it must not be crashed anymore this.mTab.removeAttribute("crashed"); } if (this._shouldShowProgress(aRequest)) { if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) { this.mTab.setAttribute("busy", "true"); - if (!(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD)) + + if (aWebProgress.isTopLevel && + !(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD)) this.mTabBrowser.setTabTitleLoading(this.mTab); } if (this.mTab.selected) this.mTabBrowser.mIsBusy = true; } } else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
--- a/browser/base/content/test/general/browser_urlbarSearchSuggestions.js +++ b/browser/base/content/test/general/browser_urlbarSearchSuggestions.js @@ -17,26 +17,28 @@ add_task(function* prepare() { // Make sure the popup is closed for the next test. gURLBar.blur(); Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed"); }); }); add_task(function* clickSuggestion() { + gBrowser.selectedTab = gBrowser.addTab(); gURLBar.focus(); yield promiseAutocompleteResultPopup("foo"); let [idx, suggestion] = yield promiseFirstSuggestion(); let item = gURLBar.popup.richlistbox.getItemAtIndex(idx); let loadPromise = promiseTabLoaded(gBrowser.selectedTab); item.click(); yield loadPromise; let uri = Services.search.currentEngine.getSubmission(suggestion).uri; Assert.ok(uri.equals(gBrowser.currentURI), "The search results page should have loaded"); + gBrowser.removeTab(gBrowser.selectedTab); }); function getFirstSuggestion() { let controller = gURLBar.popup.input.controller; let matchCount = controller.matchCount; let present = false; for (let i = 0; i < matchCount; i++) { let url = controller.getValueAt(i);
--- a/browser/base/content/test/general/browser_urlbarSearchTelemetry.js +++ b/browser/base/content/test/general/browser_urlbarSearchTelemetry.js @@ -22,36 +22,40 @@ add_task(function* prepare() { // Make sure the popup is closed for the next test. gURLBar.blur(); Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed"); }); }); add_task(function* heuristicResult() { yield compareCounts(function* () { + gBrowser.selectedTab = gBrowser.addTab(); yield promiseAutocompleteResultPopup("heuristicResult"); let action = getActionAtIndex(0); Assert.ok(!!action, "there should be an action at index 0"); Assert.equal(action.type, "searchengine", "type should be searchengine"); let item = gURLBar.popup.richlistbox.getItemAtIndex(0); let loadPromise = promiseTabLoaded(gBrowser.selectedTab); item.click(); yield loadPromise; + gBrowser.removeTab(gBrowser.selectedTab); }); }); add_task(function* searchSuggestion() { yield compareCounts(function* () { + gBrowser.selectedTab = gBrowser.addTab(); yield promiseAutocompleteResultPopup("searchSuggestion"); let idx = getFirstSuggestionIndex(); Assert.ok(idx >= 0, "there should be a first suggestion"); let item = gURLBar.popup.richlistbox.getItemAtIndex(idx); let loadPromise = promiseTabLoaded(gBrowser.selectedTab); item.click(); yield loadPromise; + gBrowser.removeTab(gBrowser.selectedTab); }); }); /** * This does three things: gets current telemetry/FHR counts, calls * clickCallback, gets telemetry/FHR counts again to compare them to the old * counts. *
--- a/browser/base/content/test/general/searchSuggestionEngine.xml +++ b/browser/base/content/test/general/searchSuggestionEngine.xml @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ --> <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> <ShortName>browser_searchSuggestionEngine searchSuggestionEngine.xml</ShortName> <Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/general/searchSuggestionEngine.sjs?{searchTerms}"/> -<Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/general/searchSuggestionEngine.sjs?{searchTerms}" rel="searchform"/> +<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform"/> </SearchPlugin>
--- a/browser/extensions/loop/bootstrap.js +++ b/browser/extensions/loop/bootstrap.js @@ -30,16 +30,17 @@ var WindowListener = { * * @param {Object} window The window to inject the integration into. */ setupBrowserUI: function(window) { let document = window.document; let gBrowser = window.gBrowser; let xhrClass = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]; let FileReader = window.FileReader; + let menuItem = null; // the "exported" symbols var LoopUI = { /** * @var {XULWidgetSingleWrapper} toolbarButton Getter for the Loop toolbarbutton * instance for this window. This should * not be used in the hidden window. */ @@ -65,30 +66,16 @@ var WindowListener = { if (browser) { delete this.browser; this.browser = browser; } return browser; }, /** - * @var {String|null} selectedTab Getter for the name of the currently selected - * tab inside the Loop panel. Will be NULL if - * the panel hasn't loaded yet. - */ - get selectedTab() { - if (!this.browser) { - return null; - } - - let selectedTab = this.browser.contentDocument.querySelector(".tab-view > .selected"); - return selectedTab && selectedTab.getAttribute("data-tab-name"); - }, - - /** * @return {Promise} */ promiseDocumentVisible(aDocument) { if (!aDocument.hidden) { return Promise.resolve(aDocument); } return new Promise((resolve) => { @@ -273,32 +260,63 @@ var WindowListener = { this.MozLoopService.initialize().catch(ex => { if (!ex.message || (!ex.message.contains("not enabled") && !ex.message.contains("not needed"))) { console.error(ex); } }); + this.addMenuItem(); + // Don't do the rest if this is for the hidden window - we don't // have a toolbar there. if (window == Services.appShell.hiddenDOMWindow) { return; } // Cleanup when the window unloads. window.addEventListener("unload", () => { Services.obs.removeObserver(this, "loop-status-changed"); }); Services.obs.addObserver(this, "loop-status-changed", false); this.updateToolbarState(); }, + /** + * Adds a menu item to the browsers' Tools menu that open the Loop panel + * when selected. + */ + addMenuItem: function() { + let menu = document.getElementById("menu_ToolsPopup"); + if (!menu || menuItem) { + return; + } + + menuItem = document.createElementNS(kNSXUL, "menuitem"); + menuItem.setAttribute("id", "menu_openLoop"); + menuItem.setAttribute("label", this._getString("loopMenuItem_label")); + menuItem.setAttribute("accesskey", this._getString("loopMenuItem_accesskey")); + + menuItem.addEventListener("command", () => this.togglePanel()); + + menu.insertBefore(menuItem, document.getElementById("sync-setup")); + }, + + /** + * Removes the menu item from the browsers' Tools menu. + */ + removeMenuItem: function() { + if (menuItem) { + menuItem.parentNode.removeChild(menuItem); + } + }, + // Implements nsIObserver observe: function(subject, topic, data) { if (topic != "loop-status-changed") { return; } this.updateToolbarState(data); }, @@ -669,17 +687,20 @@ var WindowListener = { window.LoopUI = LoopUI; }, tearDownBrowserUI: function(window) { let document = window.document; // Take any steps to remove UI or anything from the browser window // document.getElementById() etc. will work here - // XXX Add in tear-down of the panel. + if (window.LoopUI) { + window.LoopUI.removeMenuItem(); + // XXX Add in tear-down of the panel. + } }, // nsIWindowMediatorListener functions. onOpenWindow: function(xulWindow) { // A new window has opened. let domWindow = xulWindow.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindow);
--- a/browser/locales/en-US/chrome/browser/browser.dtd +++ b/browser/locales/en-US/chrome/browser/browser.dtd @@ -247,18 +247,16 @@ These should match what Safari and other <!ENTITY downloads.accesskey "D"> <!ENTITY downloads.commandkey "j"> <!ENTITY downloadsUnix.commandkey "y"> <!ENTITY addons.label "Add-ons"> <!ENTITY addons.accesskey "A"> <!ENTITY addons.commandkey "A"> <!ENTITY webapps.label "Apps"> <!ENTITY webapps.accesskey "p"> -<!ENTITY loopMenuItem.label "Start a conversation…"> -<!ENTITY loopMenuItem.accesskey "t"> <!ENTITY webDeveloperMenu.label "Web Developer"> <!ENTITY webDeveloperMenu.accesskey "W"> <!ENTITY devToolsCmd.keycode "VK_F12"> <!ENTITY devToolsCmd.keytext "F12"> <!ENTITY devtoolsConnect.label "Connect…">
--- a/browser/locales/en-US/chrome/browser/loop/loop.properties +++ b/browser/locales/en-US/chrome/browser/loop/loop.properties @@ -4,16 +4,22 @@ # Panel Strings ## LOCALIZATION NOTE(clientShortname2): This should not be localized and ## should remain "Firefox Hello" for all locales. clientShortname2=Firefox Hello clientSuperShortname=Hello +## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed +## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or +## use "..." if \u2026 doesn't suit traditions in your locale. +loopMenuItem_label=Start a conversation… +loopMenuItem_accesskey=t + ## LOCALIZATION_NOTE(sign_in_again_title_line_one, sign_in_again_title_line_two2): ## These are displayed together at the top of the panel when a user is needed to ## sign-in again. The emphesis is on the first line to get the user to sign-in again, ## and this is displayed in slightly larger font. Please arrange as necessary for ## your locale. ## {{clientShortname2}} will be replaced by the brand name for either string. sign_in_again_title_line_one=Please sign in again sign_in_again_title_line_two2=to continue using {{clientShortname2}}
--- a/browser/modules/TabGroupsMigrator.jsm +++ b/browser/modules/TabGroupsMigrator.jsm @@ -139,16 +139,19 @@ this.TabGroupsMigrator = { if (!groupData) { let title = (groupInfo[group] && groupInfo[group].title) || ""; groupData = { tabs: [], tabGroupsMigrationTitle: title, }; if (!title) { groupData.anonGroupID = ++globalAnonGroupID; + groupData.tabGroupsMigrationTitle = + gBrowserBundle.formatStringFromName("tabgroups.migration.anonGroup", + [groupData.anonGroupID], 1); } // If this is the active group, set the active group ID and add // all the already-known tabs (that didn't list a group ID), if any. if (!activeGroupID && !tab.hidden) { activeGroupID = group; groupData.tabs = tabsWithoutGroup; tabsWithoutGroup = null; } @@ -174,42 +177,43 @@ this.TabGroupsMigrator = { _createBackup(stateStr) { let dest = Services.dirsvc.get("ProfD", Ci.nsIFile); dest.append("tabgroups-session-backup.json"); let promise = OS.File.writeAtomic(dest.path, stateStr, {encoding: "utf-8"}); AsyncShutdown.webWorkersShutdown.addBlocker("TabGroupsMigrator", promise); return promise; }, + _groupSorter(a, b) { + if (!a.anonGroupID) { + return -1; + } + if (!b.anonGroupID) { + return 1; + } + return a.anonGroupID - b.anonGroupID; + }, + _bookmarkAllGroupsFromState: Task.async(function*(groupData) { // First create a folder in which to put all these bookmarks: this.bookmarkedGroupsPromise = PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.menuGuid, type: PlacesUtils.bookmarks.TYPE_FOLDER, index: 0, title: gBrowserBundle.GetStringFromName("tabgroups.migration.tabGroupBookmarkFolderName"), }).catch(Cu.reportError); let tabgroupsFolder = yield this.bookmarkedGroupsPromise; for (let [, windowGroupMap] of groupData) { - let windowGroups = [... windowGroupMap.values()].sort((a, b) => { - if (!a.anonGroupID) { - return -1; - } - if (!b.anonGroupID) { - return 1; - } - return a.anonGroupID - b.anonGroupID; - }); + let windowGroups = [... windowGroupMap.values()].sort(this._groupSorter); for (let group of windowGroups) { let groupFolder = yield PlacesUtils.bookmarks.insert({ parentGuid: tabgroupsFolder.guid, type: PlacesUtils.bookmarks.TYPE_FOLDER, - title: group.tabGroupsMigrationTitle || - gBrowserBundle.formatStringFromName("tabgroups.migration.anonGroup", [group.anonGroupID], 1), + title: group.tabGroupsMigrationTitle }).catch(Cu.reportError); for (let tab of group.tabs) { let entry = tab.entries[tab.index - 1]; yield PlacesUtils.bookmarks.insert({ parentGuid: groupFolder.guid, title: tab.title || entry.title, url: entry.url, @@ -248,22 +252,25 @@ this.TabGroupsMigrator = { // Make sure we unhide it, or it won't get restored correctly tab.hidden = false; } } // We then convert any hidden groups into windows for the state object // we show in about:tabgroupsdata if (groupInfoForWindow) { + let windowsToReturn = []; for (let groupID of hiddenGroupIDs) { let group = groupInfoForWindow.get("" + groupID); if (group) { - stateToReturn.windows.push(group); + windowsToReturn.push(group); } } + windowsToReturn.sort(this._groupSorter); + stateToReturn.windows = stateToReturn.windows.concat(windowsToReturn); } // Finally we remove tab groups data from the window: if (win.extData) { delete win.extData["tabview-group"]; delete win.extData["tabview-groups"]; delete win.extData["tabview-ui"]; delete win.extData["tabview-visibility"];
--- a/browser/modules/test/xpcshell/test_TabGroupsMigrator.js +++ b/browser/modules/test/xpcshell/test_TabGroupsMigrator.js @@ -160,27 +160,109 @@ const TEST_STATES = { } ], extData: { "tabview-group": "{\"2\":{}}", }, } ], }, + SORTING_NAMING_RESTORE_PAGE: { + windows: [ + { + tabs: [ + { + entries: [{ + url: "about:robots", + title: "Robots 1", + }], + index: 1, + hidden: false, + extData: { + "tabview-tab": "{\"groupID\":2,\"active\":true}", + }, + }, + { + entries: [{ + url: "about:robots", + title: "Robots 2", + }], + index: 1, + hidden: false, + extData: { + "tabview-tab": "{\"groupID\":2}", + }, + }, + { + entries: [{ + url: "about:robots", + title: "Robots 3", + }], + index: 1, + hidden: true, + extData: { + "tabview-tab": "{\"groupID\":13}", + }, + }, + { + entries: [{ + url: "about:robots", + title: "Robots 4", + }], + index: 1, + hidden: true, + extData: { + "tabview-tab": "{\"groupID\":15}", + }, + }, + { + entries: [{ + url: "about:robots", + title: "Robots 5", + }], + index: 1, + hidden: true, + extData: { + "tabview-tab": "{\"groupID\":16}", + }, + }, + { + entries: [{ + url: "about:robots", + title: "Robots 6", + }], + index: 1, + hidden: true, + extData: { + "tabview-tab": "{\"groupID\":17}", + }, + } + ], + extData: { + "tabview-group": "{\"2\":{},\"13\":{\"title\":\"Foopy\"}, \"15\":{\"title\":\"Barry\"}, \"16\":{}, \"17\":{}}", + "tabview-groups": "{\"nextID\":20,\"activeGroupId\":2,\"totalNumber\":5}", + "tabview-visibility": "false" + }, + }, + ] + }, }; add_task(function* gatherGroupDataTest() { let groupInfo = TabGroupsMigrator._gatherGroupData(TEST_STATES.TWO_GROUPS); Assert.equal(groupInfo.size, 1, "Information about 1 window"); let singleWinGroups = [... groupInfo.values()][0]; Assert.equal(singleWinGroups.size, 2, "2 groups"); let group2 = singleWinGroups.get("2"); Assert.ok(!!group2, "group 2 should exist"); Assert.equal(group2.tabs.length, 2, "2 tabs in group 2"); - Assert.equal(group2.tabGroupsMigrationTitle, "", "We don't assign titles to untitled groups"); + // Note that this has groupID 2 in the internal representation of tab groups, + // but because it was the first group we encountered when migrating, it was + // labeled "group 1" for the user + Assert.equal(group2.tabGroupsMigrationTitle, "Group 1", "We assign a numeric title to untitled groups"); Assert.equal(group2.anonGroupID, "1", "We mark an untitled group with an anonymous id"); let group13 = singleWinGroups.get("13"); Assert.ok(!!group13, "group 13 should exist"); Assert.equal(group13.tabs.length, 1, "1 tabs in group 13"); Assert.equal(group13.tabGroupsMigrationTitle, "Foopy", "Group with title has correct title"); Assert.ok(!("anonGroupID" in group13), "We don't mark a titled group with an anonymous id"); }); @@ -303,8 +385,22 @@ add_task(function* dealWithNoGroupInfo() Assert.equal(groupInfo.size, 1, "Should have 1 window"); let windowGroups = [...groupInfo][0][1]; Assert.equal(windowGroups.size, 1, "Window should have 1 group"); let fallbackActiveGroup = windowGroups.get("active group"); Assert.ok(fallbackActiveGroup, "Fallback group should exist"); Assert.equal(fallbackActiveGroup.tabs.length, 3, "There should be 3 tabs in the fallback group"); }); +add_task(function* groupSortingInRemovedDataUsedForRestorePage() { + let stateClone = JSON.parse(JSON.stringify(TEST_STATES.SORTING_NAMING_RESTORE_PAGE)); + let groupInfo = TabGroupsMigrator._gatherGroupData(stateClone); + let removedGroups = TabGroupsMigrator._removeHiddenTabGroupsFromState(stateClone, groupInfo); + Assert.equal(stateClone.windows.length, 1, "Should still only have 1 window"); + Assert.equal(stateClone.windows[0].tabs.length, 2, "Should now have 2 tabs"); + + let restoredWindowTitles = removedGroups.windows.map(win => win.tabGroupsMigrationTitle); + // Note that group 1 is the active group and as such it won't appear in the list of + // things the user can restore: + Assert.deepEqual(restoredWindowTitles, + ["Barry", "Foopy", "Group 2", "Group 3"]); +}); +
--- a/browser/themes/windows/jar.mn +++ b/browser/themes/windows/jar.mn @@ -90,16 +90,17 @@ browser.jar: skin/classic/browser/customizableui/customizeMode-gridTexture.png (customizableui/customizeMode-gridTexture.png) skin/classic/browser/customizableui/customizeMode-separatorHorizontal.png (customizableui/customizeMode-separatorHorizontal.png) skin/classic/browser/customizableui/customizeMode-separatorVertical.png (customizableui/customizeMode-separatorVertical.png) skin/classic/browser/customizableui/menu-arrow.svg (customizableui/menu-arrow.svg) * skin/classic/browser/customizableui/panelUIOverlay.css (customizableui/panelUIOverlay.css) skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css) skin/classic/browser/downloads/buttons.png (downloads/buttons.png) skin/classic/browser/downloads/buttons-XP.png (downloads/buttons-XP.png) + skin/classic/browser/downloads/download-glow-menuPanel.png (downloads/download-glow-menuPanel.png) skin/classic/browser/downloads/download-glow-menuPanel-XPVista7.png (downloads/download-glow-menuPanel-XPVista7.png) skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png) skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png) skin/classic/browser/downloads/download-summary.png (downloads/download-summary.png) skin/classic/browser/downloads/downloads.css (downloads/downloads.css) skin/classic/browser/feeds/feedIcon.png (feeds/feedIcon.png) skin/classic/browser/feeds/feedIcon16.png (feeds/feedIcon16.png) skin/classic/browser/feeds/feedIcon-XP.png (feeds/feedIcon-XP.png)
--- a/devtools/client/canvasdebugger/test/browser.ini +++ b/devtools/client/canvasdebugger/test/browser.ini @@ -7,16 +7,18 @@ support-files = doc_no-canvas.html doc_raf-no-canvas.html doc_simple-canvas.html doc_simple-canvas-bitmasks.html doc_simple-canvas-deep-stack.html doc_simple-canvas-transparent.html doc_webgl-bindings.html doc_webgl-enum.html + doc_webgl-drawArrays.html + doc_webgl-drawElements.html head.js [browser_canvas-actor-test-01.js] [browser_canvas-actor-test-02.js] [browser_canvas-actor-test-03.js] [browser_canvas-actor-test-04.js] [browser_canvas-actor-test-05.js] [browser_canvas-actor-test-06.js]
--- a/devtools/client/canvasdebugger/test/browser_profiling-webgl.js +++ b/devtools/client/canvasdebugger/test/browser_profiling-webgl.js @@ -3,17 +3,18 @@ /** * Tests if functions inside a single animation frame are recorded and stored * for a canvas context profiling. */ function* ifTestingSupported() { let currentTime = window.performance.now(); - let { target, front } = yield initCanvasDebuggerBackend(WEBGL_ENUM_URL); + info("Start to estimate WebGL drawArrays function."); + var { target, front } = yield initCanvasDebuggerBackend(WEBGL_DRAW_ARRAYS); let navigated = once(target, "navigate"); yield front.setup({ reload: true }); ok(true, "The front was setup up successfully."); yield navigated; ok(true, "Target automatically navigated when the front was set up."); @@ -24,18 +25,63 @@ function* ifTestingSupported() { let animationOverview = yield snapshotActor.getOverview(); ok(animationOverview, "An animation overview could be retrieved after recording."); let functionCalls = animationOverview.calls; ok(functionCalls, "An array of function call actors was sent after recording."); - is(functionCalls.length, 3, - "The number of function call actors is correct."); + + testFunctionCallTimestamp(functionCalls, currentTime); + + info("Check triangle and vertex counts in drawArrays()"); + is(animationOverview.primitive.tris, 5, "The count of triangles is correct."); + is(animationOverview.primitive.vertices, 26, "The count of vertices is correct."); + is(animationOverview.primitive.points, 4, "The count of points is correct."); + is(animationOverview.primitive.lines, 8, "The count of lines is correct."); + + yield removeTab(target.tab); + + info("Start to estimate WebGL drawElements function."); + var { target, front } = yield initCanvasDebuggerBackend(WEBGL_DRAW_ELEMENTS); + + navigated = once(target, "navigate"); + + yield front.setup({ reload: true }); + ok(true, "The front was setup up successfully."); + + yield navigated; + ok(true, "Target automatically navigated when the front was set up."); + + snapshotActor = yield front.recordAnimationFrame(); + ok(snapshotActor, + "A snapshot actor was sent after recording."); + + animationOverview = yield snapshotActor.getOverview(); + ok(animationOverview, + "An animation overview could be retrieved after recording."); + + functionCalls = animationOverview.calls; + ok(functionCalls, + "An array of function call actors was sent after recording."); + + testFunctionCallTimestamp(functionCalls, currentTime); + + info("Check triangle and vertex counts in drawElements()"); + is(animationOverview.primitive.tris, 5, "The count of triangles is correct."); + is(animationOverview.primitive.vertices, 26, "The count of vertices is correct."); + is(animationOverview.primitive.points, 4, "The count of points is correct."); + is(animationOverview.primitive.lines, 8, "The count of lines is correct."); + + yield removeTab(target.tab); + finish(); +} + +function testFunctionCallTimestamp(functionCalls, currentTime) { info("Check the timestamps of function calls"); for ( let i = 0; i < functionCalls.length-1; i += 2 ) { ok( functionCalls[i].timestamp > 0, "The timestamp of the called function is larger than 0." ); ok( functionCalls[i].timestamp < currentTime, "The timestamp has been minus the frame start time." ); ok( functionCalls[i+1].timestamp > functionCalls[i].timestamp, "The timestamp of the called function is correct." ); }
new file mode 100644 --- /dev/null +++ b/devtools/client/canvasdebugger/test/doc_webgl-drawArrays.html @@ -0,0 +1,187 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>WebGL editor test page</title> + </head> + + <body> + <canvas id="canvas" width="128" height="128"></canvas> + <script id="shader-fs" type="x-shader/x-fragment"> + precision mediump float; + uniform vec4 mtrColor; + + void main(void) { + gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0) * mtrColor; + } + </script> + <script id="shader-vs" type="x-shader/x-vertex"> + attribute vec3 aVertexPosition; + + void main(void) { + gl_PointSize = 5.0; + gl_Position = vec4(aVertexPosition, 1.0); + } + </script> + <script type="text/javascript;version=1.8"> + "use strict"; + + let canvas, gl, shaderProgram; + let triangleVertexPositionBuffer, squareVertexPositionBuffer; + + window.onload = function() { + canvas = document.querySelector("canvas"); + gl = canvas.getContext("webgl", { preserveDrawingBuffer: true }); + gl.viewportWidth = canvas.width; + gl.viewportHeight = canvas.height; + + initShaders(); + initBuffers(); + + gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); + gl.clearColor(0.0, 0.0, 0.0, 1.0); + gl.disable(gl.DEPTH_TEST); + drawScene(); + } + + function getShader(gl, id) { + var shaderScript = document.getElementById(id); + if (!shaderScript) { + return null; + } + + var str = ""; + var k = shaderScript.firstChild; + while (k) { + if (k.nodeType == 3) { + str += k.textContent; + } + k = k.nextSibling; + } + + var shader; + if (shaderScript.type == "x-shader/x-fragment") { + shader = gl.createShader(gl.FRAGMENT_SHADER); + } else if (shaderScript.type == "x-shader/x-vertex") { + shader = gl.createShader(gl.VERTEX_SHADER); + } else { + return null; + } + + gl.shaderSource(shader, str); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + alert(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; + } + + function initShaders() { + var fragmentShader = getShader(gl, "shader-fs"); + var vertexShader = getShader(gl, "shader-vs"); + + shaderProgram = gl.createProgram(); + gl.attachShader(shaderProgram, vertexShader); + gl.attachShader(shaderProgram, fragmentShader); + gl.linkProgram(shaderProgram); + + if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { + alert("Could not initialise shaders"); + } + + gl.useProgram(shaderProgram); + + shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition"); + shaderProgram.pMaterialColor = gl.getUniformLocation(shaderProgram, "mtrColor"); + gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); + } + + function initBuffers() { + // Create triangle vertex/index buffer + triangleVertexPositionBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer); + var vertices = [ + 0.0, 0.5, 0.0, + -0.5, -0.5, 0.0, + 0.5, -0.5, 0.0 + ]; + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); + triangleVertexPositionBuffer.itemSize = 3; + triangleVertexPositionBuffer.numItems = 3; + + // Create square vertex/index buffer + squareVertexPositionBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); + vertices = [ + 0.8, 0.8, 0.0, + -0.8, 0.8, 0.0, + 0.8, -0.8, 0.0, + -0.8, -0.8, 0.0 + ]; + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); + squareVertexPositionBuffer.itemSize = 3; + squareVertexPositionBuffer.numItems = 4; + } + + function drawScene() { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT); + + // DrawArrays + // -------------- + // draw square - triangle strip + gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); + gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); + gl.uniform4f(shaderProgram.pMaterialColor, 1, 1, 1, 1); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems); + + // draw square - triangle fan + gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); + gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); + gl.uniform4f(shaderProgram.pMaterialColor, 0, 1, 0, 1); + gl.drawArrays(gl.TRIANGLE_FAN, 0, squareVertexPositionBuffer.numItems); + + // draw triangle + gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer); + gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); + gl.uniform4f(shaderProgram.pMaterialColor, 1, 0, 0, 1); + gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems); + + // draw points + gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); + gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); + gl.uniform4f(shaderProgram.pMaterialColor, 1, 0, 1, 1); + gl.drawArrays(gl.POINTS, 0, squareVertexPositionBuffer.numItems); + + // draw lines + gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); + gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); + gl.uniform4f(shaderProgram.pMaterialColor, 0, 0, 1, 1); + gl.lineWidth(8.0); + gl.drawArrays(gl.LINES, 0, squareVertexPositionBuffer.numItems); + + // draw line strip + gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); + gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); + gl.uniform4f(shaderProgram.pMaterialColor, 0.9, 0.6, 0, 1); + gl.lineWidth(3.0); + gl.drawArrays(gl.LINE_STRIP, 0, squareVertexPositionBuffer.numItems); + + // draw line loop + gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer); + gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); + gl.uniform4f(shaderProgram.pMaterialColor, 0, 1, 1, 1); + gl.lineWidth(3.0); + gl.drawArrays(gl.LINE_LOOP, 0, triangleVertexPositionBuffer.numItems); + + window.requestAnimationFrame(drawScene); + } + </script> + </body> + +</html> \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/devtools/client/canvasdebugger/test/doc_webgl-drawElements.html @@ -0,0 +1,225 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>WebGL editor test page</title> + </head> + + <body> + <canvas id="canvas" width="128" height="128"></canvas> + <script id="shader-fs" type="x-shader/x-fragment"> + precision mediump float; + uniform vec4 mtrColor; + + void main(void) { + gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0) * mtrColor; + } + </script> + <script id="shader-vs" type="x-shader/x-vertex"> + attribute vec3 aVertexPosition; + + void main(void) { + gl_PointSize = 5.0; + gl_Position = vec4(aVertexPosition, 1.0); + } + </script> + <script type="text/javascript;version=1.8"> + "use strict"; + + let canvas, gl, shaderProgram; + let triangleVertexPositionBuffer, squareVertexPositionBuffer; + let triangleIndexBuffer; + let squareIndexBuffer, squareStripIndexBuffer, squareFanIndexBuffer + + window.onload = function() { + canvas = document.querySelector("canvas"); + gl = canvas.getContext("webgl", { preserveDrawingBuffer: true }); + gl.viewportWidth = canvas.width; + gl.viewportHeight = canvas.height; + + initShaders(); + initBuffers(); + + gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); + gl.clearColor(0.0, 0.0, 0.0, 1.0); + gl.disable(gl.DEPTH_TEST); + drawScene(); + } + + function getShader(gl, id) { + var shaderScript = document.getElementById(id); + if (!shaderScript) { + return null; + } + + var str = ""; + var k = shaderScript.firstChild; + while (k) { + if (k.nodeType == 3) { + str += k.textContent; + } + k = k.nextSibling; + } + + var shader; + if (shaderScript.type == "x-shader/x-fragment") { + shader = gl.createShader(gl.FRAGMENT_SHADER); + } else if (shaderScript.type == "x-shader/x-vertex") { + shader = gl.createShader(gl.VERTEX_SHADER); + } else { + return null; + } + + gl.shaderSource(shader, str); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + alert(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; + } + + function initShaders() { + var fragmentShader = getShader(gl, "shader-fs"); + var vertexShader = getShader(gl, "shader-vs"); + + shaderProgram = gl.createProgram(); + gl.attachShader(shaderProgram, vertexShader); + gl.attachShader(shaderProgram, fragmentShader); + gl.linkProgram(shaderProgram); + + if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { + alert("Could not initialise shaders"); + } + + gl.useProgram(shaderProgram); + + shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition"); + shaderProgram.pMaterialColor = gl.getUniformLocation(shaderProgram, "mtrColor"); + gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); + } + + function initBuffers() { + // Create triangle vertex/index buffer + triangleVertexPositionBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer); + var vertices = [ + 0.0, 0.5, 0.0, + -0.5, -0.5, 0.0, + 0.5, -0.5, 0.0 + ]; + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); + triangleVertexPositionBuffer.itemSize = 3; + triangleVertexPositionBuffer.numItems = 3; + + triangleIndexBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, triangleIndexBuffer); + var indices = [ + 0, 1, 2 + ]; + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW); + triangleIndexBuffer.itemSize = 1; + triangleIndexBuffer.numItems = 3; + + // Create square vertex/index buffer + squareVertexPositionBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); + vertices = [ + 0.8, 0.8, 0.0, + -0.8, 0.8, 0.0, + 0.8, -0.8, 0.0, + -0.8, -0.8, 0.0 + ]; + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); + squareVertexPositionBuffer.itemSize = 3; + squareVertexPositionBuffer.numItems = 4; + + squareIndexBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareIndexBuffer); + indices = [ + 0, 1, 2, + 1, 3, 2 + ]; + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW); + squareIndexBuffer.itemSize = 1; + squareIndexBuffer.numItems = 6; + + squareStripIndexBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareStripIndexBuffer); + indices = [ + 0, 1, 2, 3 + ]; + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW); + squareStripIndexBuffer.itemSize = 1; + squareStripIndexBuffer.numItems = 4; + + } + + function drawScene() { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT); + + // DrawElements + // -------------- + // draw triangle + gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); + gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); + gl.uniform4f(shaderProgram.pMaterialColor, 1, 1, 1, 1); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareIndexBuffer); + gl.drawElements(gl.TRIANGLES, squareIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0); + + // draw square - triangle strip + gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); + gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); + gl.uniform4f(shaderProgram.pMaterialColor, 0, 1, 0, 1); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareStripIndexBuffer); + gl.drawElements(gl.TRIANGLE_FAN, squareStripIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0); + + // draw square - triangle fan + gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer); + gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); + gl.uniform4f(shaderProgram.pMaterialColor, 1, 0, 0, 1); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, triangleIndexBuffer); + gl.drawElements(gl.TRIANGLE_FAN, triangleIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0); + + // draw points + gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); + gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); + gl.uniform4f(shaderProgram.pMaterialColor, 1, 0, 1, 1); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareStripIndexBuffer); + gl.drawElements(gl.POINTS, squareStripIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0); + + // draw lines + gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); + gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); + gl.uniform4f(shaderProgram.pMaterialColor, 0, 0, 1, 1); + gl.lineWidth(8.0); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareStripIndexBuffer); + gl.drawElements(gl.LINES, squareStripIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0); + + // draw line strip + gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); + gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); + gl.uniform4f(shaderProgram.pMaterialColor, 0.9, 0.6, 0, 1); + gl.lineWidth(3.0); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareStripIndexBuffer); + gl.drawElements(gl.LINE_STRIP, squareStripIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0); + + // draw line loop + gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer); + gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); + gl.uniform4f(shaderProgram.pMaterialColor, 0, 1, 1, 1); + gl.lineWidth(3.0); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, triangleIndexBuffer); + gl.drawElements(gl.LINE_LOOP, triangleIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0); + + window.requestAnimationFrame(drawScene); + } + </script> + </body> + +</html> \ No newline at end of file
--- a/devtools/client/canvasdebugger/test/head.js +++ b/devtools/client/canvasdebugger/test/head.js @@ -34,16 +34,18 @@ const SET_TIMEOUT_URL = EXAMPLE_URL + "d const NO_CANVAS_URL = EXAMPLE_URL + "doc_no-canvas.html"; const RAF_NO_CANVAS_URL = EXAMPLE_URL + "doc_raf-no-canvas.html"; const SIMPLE_CANVAS_URL = EXAMPLE_URL + "doc_simple-canvas.html"; const SIMPLE_BITMASKS_URL = EXAMPLE_URL + "doc_simple-canvas-bitmasks.html"; const SIMPLE_CANVAS_TRANSPARENT_URL = EXAMPLE_URL + "doc_simple-canvas-transparent.html"; const SIMPLE_CANVAS_DEEP_STACK_URL = EXAMPLE_URL + "doc_simple-canvas-deep-stack.html"; const WEBGL_ENUM_URL = EXAMPLE_URL + "doc_webgl-enum.html"; const WEBGL_BINDINGS_URL = EXAMPLE_URL + "doc_webgl-bindings.html"; +const WEBGL_DRAW_ARRAYS = EXAMPLE_URL + "doc_webgl-drawArrays.html"; +const WEBGL_DRAW_ELEMENTS = EXAMPLE_URL + "doc_webgl-drawElements.html"; const RAF_BEGIN_URL = EXAMPLE_URL + "doc_raf-begin.html"; // All tests are asynchronous. waitForExplicitFinish(); var gToolEnabled = Services.prefs.getBoolPref("devtools.canvasdebugger.enabled"); DevToolsUtils.testing = true;
--- a/devtools/client/memory/initializer.js +++ b/devtools/client/memory/initializer.js @@ -10,43 +10,47 @@ Cu.import("resource://devtools/client/sh const { require } = BrowserLoaderModule.BrowserLoader("resource://devtools/client/memory/", this); const { Task } = require("resource://gre/modules/Task.jsm"); const { createFactory, createElement } = require("devtools/client/shared/vendor/react"); const ReactDOM = require("devtools/client/shared/vendor/react-dom"); const { Provider } = require("devtools/client/shared/vendor/react-redux"); const App = createFactory(require("devtools/client/memory/app")); const Store = require("devtools/client/memory/store"); const { assert } = require("devtools/shared/DevToolsUtils"); +const Telemetry = require("devtools/client/shared/telemetry"); /** * The current target, toolbox, MemoryFront, and HeapAnalysesClient, set by this tool's host. */ var gToolbox, gTarget, gFront, gHeapAnalysesClient; /** * Variables set by `initialize()` */ -var gStore, gRoot, gApp, gProvider, unsubscribe, isHighlighted; +var gStore, gRoot, gApp, gProvider, unsubscribe, isHighlighted, telemetry; var initialize = Task.async(function*() { + telemetry = new Telemetry(); + telemetry.toolOpened("memory"); gRoot = document.querySelector("#app"); gStore = Store(); gApp = createElement(App, { toolbox: gToolbox, front: gFront, heapWorker: gHeapAnalysesClient }); gProvider = createElement(Provider, { store: gStore }, gApp); ReactDOM.render(gProvider, gRoot); unsubscribe = gStore.subscribe(onStateChange); }); var destroy = Task.async(function*() { const ok = ReactDOM.unmountComponentAtNode(gRoot); assert(ok, "Should successfully unmount the memory tool's top level React component"); + telemetry.toolClosed("memory"); unsubscribe(); - gStore, gRoot, gApp, gProvider, unsubscribe, isHighlighted = null; + gStore, gRoot, gApp, gProvider, unsubscribe, isHighlighted, telemetry = null; }); /** * Fired on any state change, currently only handles toggling * the highlighting of the tool when recording allocations. */ function onStateChange () { let isRecording = gStore.getState().allocations.recording;
--- a/devtools/client/shared/telemetry.js +++ b/devtools/client/shared/telemetry.js @@ -140,16 +140,21 @@ Telemetry.prototype = { userHistogram: "DEVTOOLS_CANVASDEBUGGER_OPENED_PER_USER_FLAG", timerHistogram: "DEVTOOLS_CANVASDEBUGGER_TIME_ACTIVE_SECONDS" }, performance: { histogram: "DEVTOOLS_JSPROFILER_OPENED_BOOLEAN", userHistogram: "DEVTOOLS_JSPROFILER_OPENED_PER_USER_FLAG", timerHistogram: "DEVTOOLS_JSPROFILER_TIME_ACTIVE_SECONDS" }, + memory: { + histogram: "DEVTOOLS_MEMORY_OPENED_BOOLEAN", + userHistogram: "DEVTOOLS_MEMORY_OPENED_PER_USER_FLAG", + timerHistogram: "DEVTOOLS_MEMORY_TIME_ACTIVE_SECONDS" + }, netmonitor: { histogram: "DEVTOOLS_NETMONITOR_OPENED_BOOLEAN", userHistogram: "DEVTOOLS_NETMONITOR_OPENED_PER_USER_FLAG", timerHistogram: "DEVTOOLS_NETMONITOR_TIME_ACTIVE_SECONDS" }, storage: { histogram: "DEVTOOLS_STORAGE_OPENED_BOOLEAN", userHistogram: "DEVTOOLS_STORAGE_OPENED_PER_USER_FLAG",
--- a/devtools/client/shared/widgets/TableWidget.js +++ b/devtools/client/shared/widgets/TableWidget.js @@ -470,17 +470,17 @@ function Column(table, id, header) { this.tbody.appendChild(this.splitter); this.column = this.document.createElementNS(HTML_NS, "div"); this.column.id = id; this.column.className = "table-widget-column"; this.wrapper.appendChild(this.column); this.header = this.document.createElementNS(XUL_NS, "label"); - this.header.className = "plain devtools-toolbar table-widget-column-header"; + this.header.className = "devtools-toolbar table-widget-column-header"; this.header.setAttribute("value", header); this.column.appendChild(this.header); if (table.headersContextMenu) { this.header.setAttribute("context", table.headersContextMenu); } this.toggleColumn = this.toggleColumn.bind(this); this.table.on(EVENTS.HEADER_CONTEXT_MENU, this.toggleColumn);
--- a/devtools/client/sourceeditor/tern/README +++ b/devtools/client/sourceeditor/tern/README @@ -2,9 +2,12 @@ This is the Tern code-analysis engine pa Tern is a stand-alone code-analysis engine for JavaScript. It is intended to be used with a code editor plugin to enhance the editor's support for intelligent JavaScript editing # Upgrade Currently used version is 0.6.2. To upgrade, download the latest release from http://ternjs.net/, and copy the files from lib/ into this directory. -You may also need to update the CodeMirror plugin found in devtools/client/sourceeditor/codemirror/tern, but it will most likely work without updating. +You may also need to update the CodeMirror plugin found in devtools/client/sourceeditor/codemirror/addon/tern, but it will most likely work without updating. + +Replace instances of `require("acorn")` with `require("acorn/acorn")` +Replace instances of `acorn/dist/` with `acorn/` \ No newline at end of file
old mode 100644 new mode 100755 --- a/devtools/client/sourceeditor/tern/condense.js +++ b/devtools/client/sourceeditor/tern/condense.js @@ -2,57 +2,57 @@ // This code can be used to, after a library has been analyzed, // extract the types defined in that library and dump them as a JSON // structure (as parsed by def.js). // The idea being that big libraries can be analyzed once, dumped, and // then cheaply included in later analysis. -(function(mod) { +(function(root, mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS return mod(exports, require("./infer")); if (typeof define == "function" && define.amd) // AMD return define(["exports", "./infer"], mod); - mod(self.tern || (self.tern = {}), tern); // Plain browser env -})(function(exports, infer) { + mod(root.tern || (root.tern = {}), tern); // Plain browser env +})(this, function(exports, infer) { "use strict"; exports.condense = function(origins, name, options) { if (typeof origins == "string") origins = [origins]; var state = new State(origins, name || origins[0], options || {}); - runPass(state.passes.preCondenseReach, state); + state.server.signal("preCondenseReach", state) state.cx.topScope.path = "<top>"; state.cx.topScope.reached("", state); for (var path in state.roots) reach(state.roots[path], null, path, state); for (var i = 0; i < state.patchUp.length; ++i) patchUpSimpleInstance(state.patchUp[i], state); - runPass(state.passes.postCondenseReach, state); + state.server.signal("postCondenseReach", state) for (var path in state.types) store(createPath(path.split("."), state), state.types[path], state); for (var path in state.altPaths) storeAlt(path, state.altPaths[path], state); var hasDef = false; for (var _def in state.output["!define"]) { hasDef = true; break; } if (!hasDef) delete state.output["!define"]; - runPass(state.passes.postCondense, state); + state.server.signal("postCondense", state) return simplify(state.output, state.options.sortOutput); }; function State(origins, name, options) { this.origins = origins; this.cx = infer.cx(); - this.passes = options.passes || this.cx.parent && this.cx.parent.passes || {}; + this.server = options.server || this.cx.parent || {signal: function() {}} this.maxOrigin = -Infinity; for (var i = 0; i < origins.length; ++i) this.maxOrigin = Math.max(this.maxOrigin, this.cx.origins.indexOf(origins[i])); this.output = {"!name": name, "!define": {}}; this.options = options; this.types = Object.create(null); this.altPaths = Object.create(null); this.patchUp = []; @@ -108,34 +108,40 @@ if (!(actual instanceof infer.Prim)) actual.path = newPath; if (actual.reached(newPath, state, !relevant) && relevant) { var data = state.types[oldPath]; if (data) { delete state.types[oldPath]; state.altPaths[oldPath] = actual; } else data = {type: actual}; data.span = state.getSpan(type) || (actual != type && state.isTarget(actual.origin) && state.getSpan(actual)) || data.span; - data.doc = type.doc || (actual != type && state.isTarget(actual.origin) && type.doc) || data.doc; + data.doc = type.doc || (actual != type && state.isTarget(actual.origin) && actual.doc) || data.doc; data.data = actual.metaData; data.byName = data.byName == null ? !!byName : data.byName && byName; state.types[newPath] = data; } } else { if (relevant) state.altPaths[newPath] = actual; } } function reachByName(aval, path, id, state) { var type = aval.getType(); if (type) reach(type, path, id, state, true); } infer.Prim.prototype.reached = function() {return true;}; infer.Arr.prototype.reached = function(path, state, concrete) { - if (!concrete) reachByName(this.getProp("<i>"), path, "<i>", state); + if (concrete) return true + if (this.tuple) { + for (var i = 0; i < this.tuple; i++) + reachByName(this.getProp(String(i)), path, String(i), state) + } else { + reachByName(this.getProp("<i>"), path, "<i>", state) + } return true; }; infer.Fn.prototype.reached = function(path, state, concrete) { infer.Obj.prototype.reached.call(this, path, state, concrete); if (!concrete) { for (var i = 0; i < this.args.length; ++i) reachByName(this.args[i], path, "!" + i, state); @@ -172,22 +178,20 @@ } for (var prop in obj.props) reach(obj.props[prop], path, prop, state); } function createPath(parts, state) { var base = state.output, defs = state.output["!define"]; for (var i = 0, path; i < parts.length; ++i) { - var part = parts[i], known = path && state.types[path]; + var part = parts[i]; path = path ? path + "." + part : part; var me = state.types[path]; - if (part.charAt(0) == "!" || - known && known.type.constructor != infer.Obj || - me && me.byName) { + if (part.charAt(0) == "!" || me && me.byName) { if (hop(defs, path)) base = defs[path]; else defs[path] = base = {}; } else { if (hop(base, parts[i])) base = base[part]; else base = base[part] = {}; } } return base; @@ -211,47 +215,65 @@ if (last[0] == "!") return; var known = state.types[parts.join(".")]; var base = createPath(parts, state); if (known && known.type.constructor != infer.Obj) return; if (!hop(base, last)) base[last] = type.nameOverride || type.path; } var typeNameStack = []; - function typeName(type) { - var actual = type.getType(false); - if (!actual || typeNameStack.indexOf(actual) > -1) - return actual && actual.path || "?"; - typeNameStack.push(actual); - var name = actual.typeName(); - typeNameStack.pop(); + function typeName(value) { + var isType = value instanceof infer.Type; + if (isType) { + if (typeNameStack.indexOf(value) > -1) + return value.path || "?"; + typeNameStack.push(value); + } + var name = value.typeName(); + if (isType) typeNameStack.pop(); return name; } + infer.AVal.prototype.typeName = function() { + if (this.types.length == 0) return "?"; + if (this.types.length == 1) return typeName(this.types[0]); + var simplified = infer.simplifyTypes(this.types); + if (simplified.length > 2) return "?"; + for (var strs = [], i = 0; i < simplified.length; i++) + strs.push(typeName(simplified[i])); + return strs.join("|"); + }; + + infer.ANull.typeName = function() { return "?"; }; + infer.Prim.prototype.typeName = function() { return this.name; }; + infer.Sym.prototype.typeName = function() { return this.asPropName } + infer.Arr.prototype.typeName = function() { - return "[" + typeName(this.getProp("<i>")) + "]"; + if (!this.tuple) return "[" + typeName(this.getProp("<i>")) + "]" + var content = [] + for (var i = 0; i < this.tuple; i++) + content.push(typeName(this.getProp(String(i)))) + return "[" + content.join(", ") + "]" }; infer.Fn.prototype.typeName = function() { - var out = "fn("; + var out = this.generator ? "fn*(" : "fn("; for (var i = 0; i < this.args.length; ++i) { if (i) out += ", "; var name = this.argNames[i]; if (name && name != "?") out += name + ": "; out += typeName(this.args[i]); } out += ")"; - if (this.computeRetSource) { + if (this.computeRetSource) out += " -> " + this.computeRetSource; - } else if (!this.retval.isEmpty()) { - var rettype = this.retval.getType(false); - if (rettype) out += " -> " + typeName(rettype); - } + else if (!this.retval.isEmpty()) + out += " -> " + typeName(this.retval); return out; }; infer.Obj.prototype.typeName = function() { if (this.nameOverride) return this.nameOverride; if (!this.path) return "?"; return this.path; }; @@ -274,14 +296,9 @@ for (var prop in obj) props.push(prop); props.sort(); for (var i = 0; i < props.length; ++i) { var prop = props[i]; out[prop] = obj[prop]; } return out; } - - function runPass(functions) { - if (functions) for (var i = 0; i < functions.length; ++i) - functions[i].apply(null, Array.prototype.slice.call(arguments, 1)); - } });
old mode 100644 new mode 100755 --- a/devtools/client/sourceeditor/tern/def.js +++ b/devtools/client/sourceeditor/tern/def.js @@ -22,149 +22,229 @@ } var TypeParser = exports.TypeParser = function(spec, start, base, forceNew) { this.pos = start || 0; this.spec = spec; this.base = base; this.forceNew = forceNew; }; + + function unwrapType(type, self, args) { + return type.call ? type(self, args) : type; + } + + function extractProp(type, prop) { + if (prop == "!ret") { + if (type.retval) return type.retval; + var rv = new infer.AVal; + type.propagate(new infer.IsCallee(infer.ANull, [], null, rv)); + return rv; + } else { + return type.getProp(prop); + } + } + + function computedFunc(name, args, retType, generator) { + return function(self, cArgs) { + var realArgs = []; + for (var i = 0; i < args.length; i++) realArgs.push(unwrapType(args[i], self, cArgs)); + return new infer.Fn(name, infer.ANull, realArgs, unwrapType(retType, self, cArgs), generator); + }; + } + function computedUnion(types) { + return function(self, args) { + var union = new infer.AVal; + for (var i = 0; i < types.length; i++) unwrapType(types[i], self, args).propagate(union); + union.maxWeight = 1e5; + return union; + }; + } + function computedArray(inner) { + return function(self, args) { + return new infer.Arr(inner(self, args)); + }; + } + function computedTuple(types) { + return function(self, args) { + return new infer.Arr(types.map(function(tp) { return unwrapType(tp, self, args) })) + } + } + TypeParser.prototype = { eat: function(str) { if (str.length == 1 ? this.spec.charAt(this.pos) == str : this.spec.indexOf(str, this.pos) == this.pos) { this.pos += str.length; return true; } }, word: function(re) { var word = "", ch, re = re || /[\w$]/; while ((ch = this.spec.charAt(this.pos)) && re.test(ch)) { word += ch; ++this.pos; } return word; }, error: function() { throw new Error("Unrecognized type spec: " + this.spec + " (at " + this.pos + ")"); }, - parseFnType: function(name, top) { - var args = [], names = []; + parseFnType: function(comp, name, top, generator) { + var args = [], names = [], computed = false; if (!this.eat(")")) for (var i = 0; ; ++i) { var colon = this.spec.indexOf(": ", this.pos), argname; if (colon != -1) { argname = this.spec.slice(this.pos, colon); if (/^[$\w?]+$/.test(argname)) this.pos = colon + 2; else argname = null; } names.push(argname); - args.push(this.parseType()); + var argType = this.parseType(comp); + if (argType.call) computed = true; + args.push(argType); if (!this.eat(", ")) { this.eat(")") || this.error(); break; } } var retType, computeRet, computeRetStart, fn; if (this.eat(" -> ")) { - if (top && this.spec.indexOf("!", this.pos) > -1) { + var retStart = this.pos; + retType = this.parseType(true); + if (retType.call && !computed) { + computeRet = retType; retType = infer.ANull; - computeRetStart = this.pos; - computeRet = this.parseRetType(); - } else retType = this.parseType(); - } else retType = infer.ANull; + computeRetStart = retStart; + } + } else { + retType = infer.ANull; + } + if (computed) return computedFunc(name, args, retType, generator); + if (top && (fn = this.base)) - infer.Fn.call(this.base, name, infer.ANull, args, names, retType); + infer.Fn.call(this.base, name, infer.ANull, args, names, retType, generator); else - fn = new infer.Fn(name, infer.ANull, args, names, retType); + fn = new infer.Fn(name, infer.ANull, args, names, retType, generator); if (computeRet) fn.computeRet = computeRet; if (computeRetStart != null) fn.computeRetSource = this.spec.slice(computeRetStart, this.pos); return fn; }, - parseType: function(name, top) { - if (this.eat("fn(")) { - return this.parseFnType(name, top); + parseType: function(comp, name, top) { + var main = this.parseTypeMaybeProp(comp, name, top); + if (!this.eat("|")) return main; + var types = [main], computed = main.call; + for (;;) { + var next = this.parseTypeMaybeProp(comp, name, top); + types.push(next); + if (next.call) computed = true; + if (!this.eat("|")) break; + } + if (computed) return computedUnion(types); + var union = new infer.AVal; + for (var i = 0; i < types.length; i++) types[i].propagate(union); + union.maxWeight = 1e5; + return union; + }, + parseTypeMaybeProp: function(comp, name, top) { + var result = this.parseTypeInner(comp, name, top); + while (comp && this.eat(".")) result = this.extendWithProp(result); + return result; + }, + extendWithProp: function(base) { + var propName = this.word(/[\w<>$!:]/) || this.error(); + if (base.apply) return function(self, args) { + return extractProp(base(self, args), propName); + }; + return extractProp(base, propName); + }, + parseTypeInner: function(comp, name, top) { + var gen + if (this.eat("fn(") || (gen = this.eat("fn*("))) { + return this.parseFnType(comp, name, top, gen); } else if (this.eat("[")) { - var inner = this.parseType(); - if (inner == infer.ANull && this.spec == "[b.<i>]") { - var b = parsePath("b"); - console.log(b.props["<i>"].types.length); + var inner = this.parseType(comp), types, computed = inner.call + while (this.eat(", ")) { + if (!types) types = [inner] + var next = this.parseType(comp) + types.push(next) + computed = computed || next.call } - this.eat("]") || this.error(); + this.eat("]") || this.error() + if (computed) return types ? computedTuple(types) : computedArray(inner) if (top && this.base) { - infer.Arr.call(this.base, inner); - return this.base; + infer.Arr.call(this.base, types || inner) + return this.base } - return new infer.Arr(inner); + return new infer.Arr(types || inner) } else if (this.eat("+")) { - var path = this.word(/[\w$<>\.!]/); - var base = parsePath(path + ".prototype"); - if (!(base instanceof infer.Obj)) base = parsePath(path); - if (!(base instanceof infer.Obj)) return base; + var path = this.word(/[\w$<>\.:!]/) + var base = infer.cx().localDefs[path + ".prototype"] + if (!base) { + var base = parsePath(path); + if (!(base instanceof infer.Obj)) return base; + var proto = descendProps(base, ["prototype"]) + if (proto && (proto = proto.getObjType())) + base = proto + } + if (comp && this.eat("[")) return this.parsePoly(base); if (top && this.forceNew) return new infer.Obj(base); return infer.getInstance(base); + } else if (this.eat(":")) { + var name = this.word(/[\w$\.]/) + return infer.getSymbol(name) + } else if (comp && this.eat("!")) { + var arg = this.word(/\d/); + if (arg) { + arg = Number(arg); + return function(_self, args) {return args[arg] || infer.ANull;}; + } else if (this.eat("this")) { + return function(self) {return self;}; + } else if (this.eat("custom:")) { + var fname = this.word(/[\w$]/); + return customFunctions[fname] || function() { return infer.ANull; }; + } else { + return this.fromWord("!" + this.word(/[\w$<>\.!:]/)); + } } else if (this.eat("?")) { return infer.ANull; } else { - return this.fromWord(this.word(/[\w$<>\.!`]/)); + return this.fromWord(this.word(/[\w$<>\.!:`]/)); } }, fromWord: function(spec) { var cx = infer.cx(); switch (spec) { case "number": return cx.num; case "string": return cx.str; case "bool": return cx.bool; case "<top>": return cx.topScope; } if (cx.localDefs && spec in cx.localDefs) return cx.localDefs[spec]; return parsePath(spec); }, - parseBaseRetType: function() { - if (this.eat("[")) { - var inner = this.parseRetType(); - this.eat("]") || this.error(); - return function(self, args) { return new infer.Arr(inner(self, args)); }; - } else if (this.eat("+")) { - var base = this.parseRetType(); - return function(self, args) { return infer.getInstance(base(self, args)); }; - } else if (this.eat("!")) { - var arg = this.word(/\d/); - if (arg) { - arg = Number(arg); - return function(_self, args) {return args[arg] || infer.ANull;}; - } else if (this.eat("this")) { - return function(self) {return self;}; - } else if (this.eat("custom:")) { - var fname = this.word(/[\w$]/); - return customFunctions[fname] || function() { return infer.ANull; }; - } else { - return this.fromWord("!" + arg + this.word(/[\w$<>\.!]/)); - } + parsePoly: function(base) { + var propName = "<i>", match; + if (match = this.spec.slice(this.pos).match(/^\s*([\w$:]+)\s*=\s*/)) { + propName = match[1]; + this.pos += match[0].length; } - var t = this.parseType(); - return function(){return t;}; - }, - extendRetType: function(base) { - var propName = this.word(/[\w<>$!]/) || this.error(); - if (propName == "!ret") return function(self, args) { - var lhs = base(self, args); - if (lhs.retval) return lhs.retval; - var rv = new infer.AVal; - lhs.propagate(new infer.IsCallee(infer.ANull, [], null, rv)); - return rv; + var value = this.parseType(true); + if (!this.eat("]")) this.error(); + if (value.call) return function(self, args) { + var instance = new infer.Obj(base); + value(self, args).propagate(instance.defProp(propName)); + return instance; }; - return function(self, args) {return base(self, args).getProp(propName);}; - }, - parseRetType: function() { - var tp = this.parseBaseRetType(); - while (this.eat(".")) tp = this.extendRetType(tp); - return tp; + var instance = new infer.Obj(base); + value.propagate(instance.defProp(propName)); + return instance; } }; function parseType(spec, name, base, forceNew) { - var type = new TypeParser(spec, null, base, forceNew).parseType(name, true); + var type = new TypeParser(spec, null, base, forceNew).parseType(false, name, true); if (/^fn\(/.test(spec)) for (var i = 0; i < type.args.length; ++i) (function(i) { var arg = type.args[i]; if (arg instanceof infer.Fn && arg.args && arg.args.length) addEffect(type, function(_self, fArgs) { var fArg = fArgs[i]; if (fArg) fArg.propagate(new infer.IsCallee(infer.cx().topScope, arg.args, null, infer.ANull)); }); })(i); return type; @@ -178,77 +258,84 @@ return replaceRet ? handled : old; }; } var parseEffect = exports.parseEffect = function(effect, fn) { var m; if (effect.indexOf("propagate ") == 0) { var p = new TypeParser(effect, 10); - var getOrigin = p.parseRetType(); + var origin = p.parseType(true); if (!p.eat(" ")) p.error(); - var getTarget = p.parseRetType(); + var target = p.parseType(true); addEffect(fn, function(self, args) { - getOrigin(self, args).propagate(getTarget(self, args)); + unwrapType(origin, self, args).propagate(unwrapType(target, self, args)); }); } else if (effect.indexOf("call ") == 0) { var andRet = effect.indexOf("and return ", 5) == 5; var p = new TypeParser(effect, andRet ? 16 : 5); - var getCallee = p.parseRetType(), getSelf = null, getArgs = []; - if (p.eat(" this=")) getSelf = p.parseRetType(); - while (p.eat(" ")) getArgs.push(p.parseRetType()); + var getCallee = p.parseType(true), getSelf = null, getArgs = []; + if (p.eat(" this=")) getSelf = p.parseType(true); + while (p.eat(" ")) getArgs.push(p.parseType(true)); addEffect(fn, function(self, args) { - var callee = getCallee(self, args); - var slf = getSelf ? getSelf(self, args) : infer.ANull, as = []; - for (var i = 0; i < getArgs.length; ++i) as.push(getArgs[i](self, args)); + var callee = unwrapType(getCallee, self, args); + var slf = getSelf ? unwrapType(getSelf, self, args) : infer.ANull, as = []; + for (var i = 0; i < getArgs.length; ++i) as.push(unwrapType(getArgs[i], self, args)); var result = andRet ? new infer.AVal : infer.ANull; callee.propagate(new infer.IsCallee(slf, as, null, result)); return result; }, andRet); } else if (m = effect.match(/^custom (\S+)\s*(.*)/)) { var customFunc = customFunctions[m[1]]; if (customFunc) addEffect(fn, m[2] ? customFunc(m[2]) : customFunc); } else if (effect.indexOf("copy ") == 0) { var p = new TypeParser(effect, 5); - var getFrom = p.parseRetType(); + var getFrom = p.parseType(true); p.eat(" "); - var getTo = p.parseRetType(); + var getTo = p.parseType(true); addEffect(fn, function(self, args) { - var from = getFrom(self, args), to = getTo(self, args); + var from = unwrapType(getFrom, self, args), to = unwrapType(getTo, self, args); from.forAllProps(function(prop, val, local) { if (local && prop != "<i>") - to.propagate(new infer.PropHasSubset(prop, val)); + to.propagate(new infer.DefProp(prop, val)); }); }); } else { throw new Error("Unknown effect type: " + effect); } }; var currentTopScope; - var parsePath = exports.parsePath = function(path) { + var parsePath = exports.parsePath = function(path, scope) { var cx = infer.cx(), cached = cx.paths[path], origPath = path; if (cached != null) return cached; cx.paths[path] = infer.ANull; - var base = currentTopScope || cx.topScope; + var base = scope || currentTopScope || cx.topScope; if (cx.localDefs) for (var name in cx.localDefs) { if (path.indexOf(name) == 0) { if (path == name) return cx.paths[path] = cx.localDefs[path]; if (path.charAt(name.length) == ".") { base = cx.localDefs[name]; path = path.slice(name.length + 1); break; } } } - var parts = path.split("."); + var result = descendProps(base, path.split(".")) + // Uncomment this to get feedback on your poorly written .json files + // if (result == infer.ANull) console.error("bad path: " + origPath + " (" + cx.curOrigin + ")") + cx.paths[origPath] = result == infer.ANull ? null : result + return result + } + + function descendProps(base, parts) { for (var i = 0; i < parts.length && base != infer.ANull; ++i) { var prop = parts[i]; if (prop.charAt(0) == "!") { if (prop == "!proto") { base = (base instanceof infer.Obj && base.proto) || infer.ANull; } else { var fn = base.getFunctionType(); if (!fn) { @@ -263,21 +350,18 @@ } else if (base instanceof infer.Obj) { var propVal = (prop == "prototype" && base instanceof infer.Fn) ? base.getProp(prop) : base.props[prop]; if (!propVal || propVal.isEmpty()) base = infer.ANull; else base = propVal.types[0]; } } - // Uncomment this to get feedback on your poorly written .json files - // if (base == infer.ANull) console.error("bad path: " + origPath + " (" + cx.curOrigin + ")"); - cx.paths[origPath] = base == infer.ANull ? null : base; return base; - }; + } function emptyObj(ctor) { var empty = Object.create(ctor.prototype); empty.props = Object.create(null); empty.isShell = true; return empty; } @@ -303,17 +387,17 @@ } base.name = path; } for (var name in spec) if (hop(spec, name) && name.charCodeAt(0) != 33) { var inner = spec[name]; if (typeof inner == "string" || isSimpleAnnotation(inner)) continue; var prop = base.defProp(name); - passOne(prop.getType(false), inner, path ? path + "." + name : name).propagate(prop); + passOne(prop.getObjType(), inner, path ? path + "." + name : name).propagate(prop); } return base; } function passTwo(base, spec, path) { if (base.isShell) { delete base.isShell; var tp = spec["!type"]; @@ -327,54 +411,47 @@ var effects = spec["!effects"]; if (effects && base instanceof infer.Fn) for (var i = 0; i < effects.length; ++i) parseEffect(effects[i], base); copyInfo(spec, base); for (var name in spec) if (hop(spec, name) && name.charCodeAt(0) != 33) { var inner = spec[name], known = base.defProp(name), innerPath = path ? path + "." + name : name; - var type = known.getType(false); if (typeof inner == "string") { - if (type) continue; - parseType(inner, innerPath).propagate(known); + if (known.isEmpty()) parseType(inner, innerPath).propagate(known); } else { - if (!isSimpleAnnotation(inner)) { - passTwo(type, inner, innerPath); - } else if (!type) { + if (!isSimpleAnnotation(inner)) + passTwo(known.getObjType(), inner, innerPath); + else if (known.isEmpty()) parseType(inner["!type"], innerPath, null, true).propagate(known); - type = known.getType(false); - if (type instanceof infer.Obj) copyInfo(inner, type); - } else continue; + else + continue; if (inner["!doc"]) known.doc = inner["!doc"]; if (inner["!url"]) known.url = inner["!url"]; if (inner["!span"]) known.span = inner["!span"]; } } + return base; } function copyInfo(spec, type) { if (spec["!doc"]) type.doc = spec["!doc"]; if (spec["!url"]) type.url = spec["!url"]; if (spec["!span"]) type.span = spec["!span"]; if (spec["!data"]) type.metaData = spec["!data"]; } - function runPasses(type, arg) { - var parent = infer.cx().parent, pass = parent && parent.passes && parent.passes[type]; - if (pass) for (var i = 0; i < pass.length; i++) pass[i](arg); - } - function doLoadEnvironment(data, scope) { - var cx = infer.cx(); + var cx = infer.cx(), server = cx.parent infer.addOrigin(cx.curOrigin = data["!name"] || "env#" + cx.origins.length); cx.localDefs = cx.definitions[cx.curOrigin] = Object.create(null); - runPasses("preLoadDef", data); + if (server) server.signal("preLoadDef", data) passOne(scope, data); var def = data["!define"]; if (def) { for (var name in def) { var spec = def[name]; cx.localDefs[name] = typeof spec == "string" ? parsePath(spec) : passOne(null, spec, name); @@ -382,42 +459,64 @@ for (var name in def) { var spec = def[name]; if (typeof spec != "string") passTwo(cx.localDefs[name], def[name], name); } } passTwo(scope, data); - runPasses("postLoadDef", data); + if (server) server.signal("postLoadDef", data) cx.curOrigin = cx.localDefs = null; } exports.load = function(data, scope) { if (!scope) scope = infer.cx().topScope; var oldScope = currentTopScope; currentTopScope = scope; try { doLoadEnvironment(data, scope); } finally { currentTopScope = oldScope; } }; + exports.parse = function(data, origin, path) { + var cx = infer.cx(); + if (origin) { + cx.origin = origin; + cx.localDefs = cx.definitions[origin]; + } + + try { + if (typeof data == "string") + return parseType(data, path); + else + return passTwo(passOne(null, data, path), data, path); + } finally { + if (origin) cx.origin = cx.localDefs = null; + } + }; + // Used to register custom logic for more involved effect or type // computation. var customFunctions = Object.create(null); infer.registerFunction = function(name, f) { customFunctions[name] = f; }; - var IsCreated = infer.constraint("created, target, spec", { + var IsCreated = infer.constraint({ + construct: function(created, target, spec) { + this.created = created; + this.target = target; + this.spec = spec; + }, addType: function(tp) { if (tp instanceof infer.Obj && this.created++ < 5) { var derived = new infer.Obj(tp), spec = this.spec; - if (spec instanceof infer.AVal) spec = spec.getType(false); + if (spec instanceof infer.AVal) spec = spec.getObjType(false); if (spec instanceof infer.Obj) for (var prop in spec.props) { var cur = spec.props[prop].types[0]; var p = derived.defProp(prop); if (cur && cur instanceof infer.Obj && cur.props.value) { var vtp = cur.props.value.getType(false); if (vtp) p.addType(vtp); } } @@ -430,21 +529,58 @@ if (argNodes && argNodes.length && argNodes[0].type == "Literal" && argNodes[0].value == null) return new infer.Obj(); var result = new infer.AVal; if (args[0]) args[0].propagate(new IsCreated(0, result, args[1])); return result; }); - var IsBound = infer.constraint("self, args, target", { + var PropSpec = infer.constraint({ + construct: function(target) { this.target = target; }, + addType: function(tp) { + if (!(tp instanceof infer.Obj)) return; + if (tp.hasProp("value")) + tp.getProp("value").propagate(this.target); + else if (tp.hasProp("get")) + tp.getProp("get").propagate(new infer.IsCallee(infer.ANull, [], null, this.target)); + } + }); + + infer.registerFunction("Object_defineProperty", function(_self, args, argNodes) { + if (argNodes && argNodes.length >= 3 && argNodes[1].type == "Literal" && + typeof argNodes[1].value == "string") { + var obj = args[0], connect = new infer.AVal; + obj.propagate(new infer.DefProp(argNodes[1].value, connect, argNodes[1])); + args[2].propagate(new PropSpec(connect)); + } + return infer.ANull; + }); + + infer.registerFunction("Object_defineProperties", function(_self, args, argNodes) { + if (args.length >= 2) { + var obj = args[0]; + args[1].forAllProps(function(prop, val, local) { + if (!local) return; + var connect = new infer.AVal; + obj.propagate(new infer.DefProp(prop, connect, argNodes && argNodes[1])); + val.propagate(new PropSpec(connect)); + }); + } + return infer.ANull; + }); + + var IsBound = infer.constraint({ + construct: function(self, args, target) { + this.self = self; this.args = args; this.target = target; + }, addType: function(tp) { if (!(tp instanceof infer.Fn)) return; - this.target.addType(new infer.Fn(tp.name, tp.self, tp.args.slice(this.args.length), - tp.argNames.slice(this.args.length), tp.retval)); + this.target.addType(new infer.Fn(tp.name, infer.ANull, tp.args.slice(this.args.length), + tp.argNames.slice(this.args.length), tp.retval, tp.generator)); this.self.propagate(tp.self); for (var i = 0; i < Math.min(tp.args.length, this.args.length); ++i) this.args[i].propagate(tp.args[i]); } }); infer.registerFunction("Function_bind", function(self, args) { if (!args.length) return infer.ANull; @@ -457,10 +593,64 @@ var arr = new infer.Arr; if (args.length != 1 || !args[0].hasType(infer.cx().num)) { var content = arr.getProp("<i>"); for (var i = 0; i < args.length; ++i) args[i].propagate(content); } return arr; }); + infer.registerFunction("Promise_ctor", function(_self, args, argNodes) { + var defs6 = infer.cx().definitions.ecma6 + if (!defs6 || args.length < 1) return infer.ANull; + var self = new infer.Obj(defs6["Promise.prototype"]); + var valProp = self.defProp(":t", argNodes && argNodes[0]); + var valArg = new infer.AVal; + valArg.propagate(valProp); + var exec = new infer.Fn("execute", infer.ANull, [valArg], ["value"], infer.ANull); + var reject = defs6.Promise_reject; + args[0].propagate(new infer.IsCallee(infer.ANull, [exec, reject], null, infer.ANull)); + return self; + }); + + var PromiseResolvesTo = infer.constraint({ + construct: function(output) { this.output = output; }, + addType: function(tp) { + if (tp.constructor == infer.Obj && tp.name == "Promise" && tp.hasProp(":t")) + tp.getProp(":t").propagate(this.output); + else + tp.propagate(this.output); + } + }); + + var WG_PROMISE_KEEP_VALUE = 50; + + infer.registerFunction("Promise_then", function(self, args, argNodes) { + var fn = args.length && args[0].getFunctionType(); + var defs6 = infer.cx().definitions.ecma6 + if (!fn || !defs6) return self; + + var result = new infer.Obj(defs6["Promise.prototype"]); + var value = result.defProp(":t", argNodes && argNodes[0]), ty; + if (fn.retval.isEmpty() && (ty = self.getType()) instanceof infer.Obj && ty.hasProp(":t")) + ty.getProp(":t").propagate(value, WG_PROMISE_KEEP_VALUE); + fn.retval.propagate(new PromiseResolvesTo(value)); + return result; + }); + + infer.registerFunction("getOwnPropertySymbols", function(_self, args) { + if (!args.length) return infer.ANull + var result = new infer.AVal + args[0].forAllProps(function(prop, _val, local) { + if (local && prop.charAt(0) == ":") result.addType(infer.getSymbol(prop.slice(1))) + }) + return result + }) + + infer.registerFunction("getSymbol", function(_self, _args, argNodes) { + if (argNodes.length && argNodes[0].type == "Literal" && typeof argNodes[0].value == "string") + return infer.getSymbol(argNodes[0].value) + else + return infer.ANull + }) + return exports; });
old mode 100644 new mode 100755 --- a/devtools/client/sourceeditor/tern/infer.js +++ b/devtools/client/sourceeditor/tern/infer.js @@ -1,65 +1,71 @@ // Main type inference engine -// Walks an AST, building up a graph of abstract values and contraints +// Walks an AST, building up a graph of abstract values and constraints // that cause types to flow from one node to another. Also defines a // number of utilities for accessing ASTs and scopes. // Analysis is done in a context, which is tracked by the dynamically // bound cx variable. Use withContext to set the current context. // For memory-saving reasons, individual types export an interface // similar to abstract values (which can hold multiple types), and can // thus be used in place abstract values that only ever contain a // single type. -(function(mod) { +(function(root, mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS - return mod(exports, require("acorn/acorn"), require("acorn/acorn_loose"), require("acorn/util/walk"), + return mod(exports, require("acorn/acorn"), require("acorn/acorn_loose"), require("acorn/walk"), require("./def"), require("./signal")); if (typeof define == "function" && define.amd) // AMD - return define(["exports", "acorn/acorn", "acorn/acorn_loose", "acorn/util/walk", "./def", "./signal"], mod); - mod(self.tern || (self.tern = {}), acorn, acorn, acorn.walk, tern.def, tern.signal); // Plain browser env -})(function(exports, acorn, acorn_loose, walk, def, signal) { + return define(["exports", "acorn/acorn", "acorn/acorn_loose", "acorn/walk", "./def", "./signal"], mod); + mod(root.tern || (root.tern = {}), acorn, acorn, acorn.walk, tern.def, tern.signal); // Plain browser env +})(this, function(exports, acorn, acorn_loose, walk, def, signal) { "use strict"; var toString = exports.toString = function(type, maxDepth, parent) { - return !type || type == parent ? "?": type.toString(maxDepth); + if (!type || type == parent || maxDepth && maxDepth < -3) return "?"; + return type.toString(maxDepth, parent); }; // A variant of AVal used for unknown, dead-end values. Also serves // as prototype for AVals, Types, and Constraints because it // implements 'empty' versions of all the methods that the code // expects. var ANull = exports.ANull = signal.mixin({ addType: function() {}, propagate: function() {}, getProp: function() { return ANull; }, forAllProps: function() {}, hasType: function() { return false; }, isEmpty: function() { return true; }, getFunctionType: function() {}, + getObjType: function() {}, + getSymbolType: function() {}, getType: function() {}, gatherProperties: function() {}, propagatesTo: function() {}, typeHint: function() {}, - propHint: function() {} + propHint: function() {}, + toString: function() { return "?"; } }); function extend(proto, props) { var obj = Object.create(proto); if (props) for (var prop in props) obj[prop] = props[prop]; return obj; } // ABSTRACT VALUES - var WG_DEFAULT = 100, WG_NEW_INSTANCE = 90, WG_MADEUP_PROTO = 10, WG_MULTI_MEMBER = 5, - WG_CATCH_ERROR = 5, WG_GLOBAL_THIS = 90, WG_SPECULATIVE_THIS = 2; + var WG_DEFAULT = 100, WG_NEW_INSTANCE = 90, WG_MADEUP_PROTO = 10, + WG_MULTI_MEMBER = 6, WG_CATCH_ERROR = 6, + WG_PHANTOM_OBJ = 1, + WG_GLOBAL_THIS = 90, WG_SPECULATIVE_THIS = 2, WG_SPECULATIVE_PROTO_THIS = 4; var AVal = exports.AVal = function() { this.types = []; this.forward = null; this.maxWeight = 0; }; AVal.prototype = extend(ANull, { addType: function(type, weight) { @@ -76,63 +82,99 @@ this.types.push(type); var forward = this.forward; if (forward) withWorklist(function(add) { for (var i = 0; i < forward.length; ++i) add(type, forward[i], weight); }); }, propagate: function(target, weight) { - if (target == ANull || (target instanceof Type)) return; - if (weight && weight < WG_DEFAULT) target = new Muffle(target, weight); + if (target == ANull || (target instanceof Type && this.forward && this.forward.length > 2)) return; + if (weight && weight != WG_DEFAULT) target = new Muffle(target, weight); (this.forward || (this.forward = [])).push(target); var types = this.types; if (types.length) withWorklist(function(add) { for (var i = 0; i < types.length; ++i) add(types[i], target, weight); }); }, getProp: function(prop) { if (prop == "__proto__" || prop == "✖") return ANull; var found = (this.props || (this.props = Object.create(null)))[prop]; if (!found) { found = this.props[prop] = new AVal; - this.propagate(new PropIsSubset(prop, found)); + this.propagate(new GetProp(prop, found)); } return found; }, forAllProps: function(c) { this.propagate(new ForAllProps(c)); }, hasType: function(type) { return this.types.indexOf(type) > -1; }, - isEmpty: function() { return this.types.length == 0; }, + isEmpty: function() { return this.types.length === 0; }, getFunctionType: function() { for (var i = this.types.length - 1; i >= 0; --i) if (this.types[i] instanceof Fn) return this.types[i]; }, + getObjType: function() { + var seen = null; + for (var i = this.types.length - 1; i >= 0; --i) { + var type = this.types[i]; + if (!(type instanceof Obj)) continue; + if (type.name) return type; + if (!seen) seen = type; + } + return seen; + }, + + getSymbolType: function() { + for (var i = this.types.length - 1; i >= 0; --i) + if (this.types[i] instanceof Sym) return this.types[i] + }, getType: function(guess) { - if (this.types.length == 0 && guess !== false) return this.makeupType(); - if (this.types.length == 1) return this.types[0]; + if (this.types.length === 0 && guess !== false) return this.makeupType(); + if (this.types.length === 1) return this.types[0]; return canonicalType(this.types); }, - computedPropType: function() { - if (!this.propertyOf || !this.propertyOf.hasProp("<i>")) return null; - var computedProp = this.propertyOf.getProp("<i>"); - if (computedProp == this) return null; - return computedProp.getType(); + toString: function(maxDepth, parent) { + if (this.types.length == 0) return toString(this.makeupType(), maxDepth, parent); + if (this.types.length == 1) return toString(this.types[0], maxDepth, parent); + var simplified = simplifyTypes(this.types); + if (simplified.length > 2) return "?"; + return simplified.map(function(tp) { return toString(tp, maxDepth, parent); }).join("|"); + }, + + makeupPropType: function(obj) { + var propName = this.propertyName; + + var protoProp = obj.proto && obj.proto.hasProp(propName); + if (protoProp) { + var fromProto = protoProp.getType(); + if (fromProto) return fromProto; + } + + if (propName != "<i>") { + var computedProp = obj.hasProp("<i>"); + if (computedProp) return computedProp.getType(); + } else if (obj.props["<i>"] != this) { + for (var prop in obj.props) { + var val = obj.props[prop]; + if (!val.isEmpty()) return val.getType(); + } + } }, makeupType: function() { - var computed = this.computedPropType(); + var computed = this.propertyOf && this.makeupPropType(this.propertyOf); if (computed) return computed; if (!this.forward) return null; for (var i = this.forward.length - 1; i >= 0; --i) { var hint = this.forward[i].typeHint(); if (hint && !hint.isEmpty()) {guessing = true; return hint;} } @@ -173,16 +215,69 @@ var prop = this.forward[i].propHint(); if (prop) f(prop, null, 0); } var guessed = this.makeupType(); if (guessed) guessed.gatherProperties(f); } }); + function similarAVal(a, b, depth) { + var typeA = a.getType(false), typeB = b.getType(false); + if (!typeA || !typeB) return true; + return similarType(typeA, typeB, depth); + } + + function similarType(a, b, depth) { + if (!a || depth >= 5) return b; + if (!a || a == b) return a; + if (!b) return a; + if (a.constructor != b.constructor) return false; + if (a.constructor == Arr) { + var innerA = a.getProp("<i>").getType(false); + if (!innerA) return b; + var innerB = b.getProp("<i>").getType(false); + if (!innerB || similarType(innerA, innerB, depth + 1)) return b; + } else if (a.constructor == Obj) { + var propsA = 0, propsB = 0, same = 0; + for (var prop in a.props) { + propsA++; + if (prop in b.props && similarAVal(a.props[prop], b.props[prop], depth + 1)) + same++; + } + for (var prop in b.props) propsB++; + if (propsA && propsB && same < Math.max(propsA, propsB) / 2) return false; + return propsA > propsB ? a : b; + } else if (a.constructor == Fn) { + if (a.args.length != b.args.length || + !a.args.every(function(tp, i) { return similarAVal(tp, b.args[i], depth + 1); }) || + !similarAVal(a.retval, b.retval, depth + 1) || !similarAVal(a.self, b.self, depth + 1)) + return false; + return a; + } else { + return false; + } + } + + var simplifyTypes = exports.simplifyTypes = function(types) { + var found = []; + outer: for (var i = 0; i < types.length; ++i) { + var tp = types[i]; + for (var j = 0; j < found.length; j++) { + var similar = similarType(tp, found[j], 0); + if (similar) { + found[j] = similar; + continue outer; + } + } + found.push(tp); + } + return found; + }; + function canonicalType(types) { var arrays = 0, fns = 0, objs = 0, prim = null; for (var i = 0; i < types.length; ++i) { var tp = types[i]; if (tp instanceof Arr) ++arrays; else if (tp instanceof Fn) ++fns; else if (tp instanceof Obj) ++objs; else if (tp instanceof Prim) { @@ -208,115 +303,121 @@ } if (score >= maxScore) { maxScore = score; maxTp = tp; } } return maxTp; } // PROPAGATION STRATEGIES - function Constraint() {} - Constraint.prototype = extend(ANull, { - init: function() { this.origin = cx.curOrigin; } - }); - - var constraint = exports.constraint = function(props, methods) { - var body = "this.init();"; - props = props ? props.split(", ") : []; - for (var i = 0; i < props.length; ++i) - body += "this." + props[i] + " = " + props[i] + ";"; - var ctor = Function.apply(null, props.concat([body])); - ctor.prototype = Object.create(Constraint.prototype); + var constraint = exports.constraint = function(methods) { + var ctor = function() { + this.origin = cx.curOrigin; + this.construct.apply(this, arguments); + }; + ctor.prototype = Object.create(ANull); for (var m in methods) if (methods.hasOwnProperty(m)) ctor.prototype[m] = methods[m]; return ctor; }; - var PropIsSubset = constraint("prop, target", { + var GetProp = constraint({ + construct: function(prop, target) { + this.prop = prop; this.target = target; + }, addType: function(type, weight) { if (type.getProp) type.getProp(this.prop).propagate(this.target, weight); }, propHint: function() { return this.prop; }, propagatesTo: function() { if (this.prop == "<i>" || !/[^\w_]/.test(this.prop)) return {target: this.target, pathExt: "." + this.prop}; } }); - var PropHasSubset = exports.PropHasSubset = constraint("prop, type, originNode", { + var DefProp = exports.PropHasSubset = exports.DefProp = constraint({ + construct: function(prop, type, originNode) { + this.prop = prop; this.type = type; this.originNode = originNode; + }, addType: function(type, weight) { if (!(type instanceof Obj)) return; var prop = type.defProp(this.prop, this.originNode); - prop.origin = this.origin; + if (!prop.origin) prop.origin = this.origin; this.type.propagate(prop, weight); }, propHint: function() { return this.prop; } }); - var ForAllProps = constraint("c", { + var ForAllProps = constraint({ + construct: function(c) { this.c = c; }, addType: function(type) { if (!(type instanceof Obj)) return; type.forAllProps(this.c); } }); function withDisabledComputing(fn, body) { cx.disabledComputing = {fn: fn, prev: cx.disabledComputing}; - try { - return body(); - } finally { - cx.disabledComputing = cx.disabledComputing.prev; - } + var result = body(); + cx.disabledComputing = cx.disabledComputing.prev; + return result; } - var IsCallee = exports.IsCallee = constraint("self, args, argNodes, retval", { - init: function() { - Constraint.prototype.init(); + var IsCallee = exports.IsCallee = constraint({ + construct: function(self, args, argNodes, retval) { + this.self = self; this.args = args; this.argNodes = argNodes; this.retval = retval; this.disabled = cx.disabledComputing; }, addType: function(fn, weight) { if (!(fn instanceof Fn)) return; for (var i = 0; i < this.args.length; ++i) { if (i < fn.args.length) this.args[i].propagate(fn.args[i], weight); if (fn.arguments) this.args[i].propagate(fn.arguments, weight); } this.self.propagate(fn.self, this.self == cx.topScope ? WG_GLOBAL_THIS : weight); - var compute = fn.computeRet; + var compute = fn.computeRet, result = fn.retval if (compute) for (var d = this.disabled; d; d = d.prev) - if (d.fn == fn || fn.name && d.fn.name == fn.name) compute = null; - if (compute) - compute(this.self, this.args, this.argNodes).propagate(this.retval, weight); - else - fn.retval.propagate(this.retval, weight); + if (d.fn == fn || fn.originNode && d.fn.originNode == fn.originNode) compute = null; + if (compute) { + var old = cx.disabledComputing; + cx.disabledComputing = this.disabled; + result = compute(this.self, this.args, this.argNodes) + cx.disabledComputing = old; + } + maybeIterator(fn, result).propagate(this.retval, weight) }, typeHint: function() { var names = []; for (var i = 0; i < this.args.length; ++i) names.push("?"); return new Fn(null, this.self, this.args, names, ANull); }, propagatesTo: function() { return {target: this.retval, pathExt: ".!ret"}; } }); - var HasMethodCall = constraint("propName, args, argNodes, retval", { - init: function() { - Constraint.prototype.init(); + var HasMethodCall = constraint({ + construct: function(propName, args, argNodes, retval) { + this.propName = propName; this.args = args; this.argNodes = argNodes; this.retval = retval; this.disabled = cx.disabledComputing; }, addType: function(obj, weight) { var callee = new IsCallee(obj, this.args, this.argNodes, this.retval); callee.disabled = this.disabled; obj.getProp(this.propName).propagate(callee, weight); }, propHint: function() { return this.propName; } }); - var IsCtor = exports.IsCtor = constraint("target, noReuse", { + var IsCtor = exports.IsCtor = constraint({ + construct: function(target, noReuse) { + this.target = target; this.noReuse = noReuse; + }, addType: function(f, weight) { if (!(f instanceof Fn)) return; + if (cx.parent && !cx.parent.options.reuseInstances) this.noReuse = true; f.getProp("prototype").propagate(new IsProto(this.noReuse ? false : f, this.target), weight); } }); var getInstance = exports.getInstance = function(obj, ctor) { if (ctor === false) return new Obj(obj); if (!ctor) ctor = obj.hasCtor; @@ -326,65 +427,85 @@ if (cur.ctor == ctor) return cur.instance; } var instance = new Obj(obj, ctor && ctor.name); instance.origin = obj.origin; obj.instances.push({ctor: ctor, instance: instance}); return instance; }; - var IsProto = exports.IsProto = constraint("ctor, target", { + var IsProto = exports.IsProto = constraint({ + construct: function(ctor, target) { + this.ctor = ctor; this.target = target; + }, addType: function(o, _weight) { if (!(o instanceof Obj)) return; if ((this.count = (this.count || 0) + 1) > 8) return; if (o == cx.protos.Array) this.target.addType(new Arr); else this.target.addType(getInstance(o, this.ctor)); } }); - var FnPrototype = constraint("fn", { + var FnPrototype = constraint({ + construct: function(fn) { this.fn = fn; }, addType: function(o, _weight) { if (o instanceof Obj && !o.hasCtor) { o.hasCtor = this.fn; var adder = new SpeculativeThis(o, this.fn); adder.addType(this.fn); o.forAllProps(function(_prop, val, local) { if (local) val.propagate(adder); }); } } }); - var IsAdded = constraint("other, target", { + var IsAdded = constraint({ + construct: function(other, target) { + this.other = other; this.target = target; + }, addType: function(type, weight) { if (type == cx.str) this.target.addType(cx.str, weight); else if (type == cx.num && this.other.hasType(cx.num)) this.target.addType(cx.num, weight); }, typeHint: function() { return this.other; } }); - var IfObj = exports.IfObj = constraint("target", { + var IfObj = exports.IfObj = constraint({ + construct: function(target) { this.target = target; }, addType: function(t, weight) { if (t instanceof Obj) this.target.addType(t, weight); }, propagatesTo: function() { return this.target; } }); - var SpeculativeThis = constraint("obj, ctor", { + var SpeculativeThis = constraint({ + construct: function(obj, ctor) { this.obj = obj; this.ctor = ctor; }, addType: function(tp) { - if (tp instanceof Fn && tp.self && tp.self.isEmpty()) - tp.self.addType(getInstance(this.obj, this.ctor), WG_SPECULATIVE_THIS); + if (tp instanceof Fn && tp.self) + tp.self.addType(getInstance(this.obj, this.ctor), WG_SPECULATIVE_PROTO_THIS); } }); - var Muffle = constraint("inner, weight", { + var HasProto = constraint({ + construct: function(obj) { this.obj = obj }, + addType: function(tp) { + if (tp instanceof Obj && this.obj.proto == cx.protos.Object) + this.obj.replaceProto(tp) + } + }); + + var Muffle = constraint({ + construct: function(inner, weight) { + this.inner = inner; this.weight = weight; + }, addType: function(tp, weight) { this.inner.addType(tp, Math.min(weight, this.weight)); }, propagatesTo: function() { return this.inner.propagatesTo(); }, typeHint: function() { return this.inner.typeHint(); }, propHint: function() { return this.inner.propHint(); } }); @@ -405,81 +526,93 @@ constructor: Prim, toString: function() { return this.name; }, getProp: function(prop) {return this.proto.hasProp(prop) || ANull;}, gatherProperties: function(f, depth) { if (this.proto) this.proto.gatherProperties(f, depth); } }); + function isInteger(str) { + var c0 = str.charCodeAt(0) + if (c0 >= 48 && c0 <= 57) return !/\D/.test(str) + else return false + } + var Obj = exports.Obj = function(proto, name) { if (!this.props) this.props = Object.create(null); this.proto = proto === true ? cx.protos.Object : proto; if (proto && !name && proto.name && !(this instanceof Fn)) { var match = /^(.*)\.prototype$/.exec(this.proto.name); if (match) name = match[1]; } this.name = name; this.maybeProps = null; this.origin = cx.curOrigin; }; Obj.prototype = extend(Type.prototype, { constructor: Obj, toString: function(maxDepth) { - if (!maxDepth && this.name) return this.name; + if (maxDepth == null) maxDepth = 0; + if (maxDepth <= 0 && this.name) return this.name; var props = [], etc = false; for (var prop in this.props) if (prop != "<i>") { if (props.length > 5) { etc = true; break; } if (maxDepth) - props.push(prop + ": " + toString(this.props[prop].getType(), maxDepth - 1)); + props.push(prop + ": " + toString(this.props[prop], maxDepth - 1, this)); else props.push(prop); } props.sort(); if (etc) props.push("..."); return "{" + props.join(", ") + "}"; }, hasProp: function(prop, searchProto) { + if (isInteger(prop)) prop = this.normalizeIntegerProp(prop) var found = this.props[prop]; if (searchProto !== false) for (var p = this.proto; p && !found; p = p.proto) found = p.props[prop]; return found; }, defProp: function(prop, originNode) { var found = this.hasProp(prop, false); if (found) { - if (found.maybePurge) found.maybePurge = false; if (originNode && !found.originNode) found.originNode = originNode; return found; } if (prop == "__proto__" || prop == "✖") return ANull; + if (isInteger(prop)) prop = this.normalizeIntegerProp(prop) var av = this.maybeProps && this.maybeProps[prop]; if (av) { delete this.maybeProps[prop]; this.maybeUnregProtoPropHandler(); } else { av = new AVal; av.propertyOf = this; + av.propertyName = prop; } this.props[prop] = av; av.originNode = originNode; av.origin = cx.curOrigin; this.broadcastProp(prop, av, true); return av; }, getProp: function(prop) { var found = this.hasProp(prop, true) || (this.maybeProps && this.maybeProps[prop]); if (found) return found; if (prop == "__proto__" || prop == "✖") return ANull; + if (isInteger(prop)) prop = this.normalizeIntegerProp(prop) var av = this.ensureMaybeProps()[prop] = new AVal; av.propertyOf = this; + av.propertyName = prop; return av; }, + normalizeIntegerProp: function(_) { return "<i>" }, broadcastProp: function(prop, val, local) { if (local) { this.signal("addProp", prop, val); // If this is a scope, it shouldn't be registered if (!(this instanceof Scope)) registerProp(prop, this); } if (this.onNewProp) for (var i = 0; i < this.onNewProp.length; ++i) { @@ -491,27 +624,35 @@ var maybe = this.maybeProps && this.maybeProps[prop]; if (maybe) { delete this.maybeProps[prop]; this.maybeUnregProtoPropHandler(); this.proto.getProp(prop).propagate(maybe); } this.broadcastProp(prop, val, false); }, + replaceProto: function(proto) { + if (this.proto && this.maybeProps) + this.proto.unregPropHandler(this) + this.proto = proto + if (this.maybeProps) + this.proto.forAllProps(this) + }, ensureMaybeProps: function() { if (!this.maybeProps) { if (this.proto) this.proto.forAllProps(this); this.maybeProps = Object.create(null); } return this.maybeProps; }, removeProp: function(prop) { var av = this.props[prop]; delete this.props[prop]; this.ensureMaybeProps()[prop] = av; + av.types.length = 0; }, forAllProps: function(c) { if (!this.onNewProp) { this.onNewProp = []; if (this.proto) this.proto.forAllProps(this); } this.onNewProp.push(c); for (var o = this; o; o = o.proto) for (var prop in o.props) { @@ -530,43 +671,45 @@ this.proto.unregPropHandler(this); }, unregPropHandler: function(handler) { for (var i = 0; i < this.onNewProp.length; ++i) if (this.onNewProp[i] == handler) { this.onNewProp.splice(i, 1); break; } this.maybeUnregProtoPropHandler(); }, gatherProperties: function(f, depth) { - for (var prop in this.props) if (prop != "<i>") + for (var prop in this.props) if (prop != "<i>" && prop.charAt(0) != ":") f(prop, this, depth); if (this.proto) this.proto.gatherProperties(f, depth + 1); - } + }, + getObjType: function() { return this; } }); - var Fn = exports.Fn = function(name, self, args, argNames, retval) { + var Fn = exports.Fn = function(name, self, args, argNames, retval, generator) { Obj.call(this, cx.protos.Function, name); this.self = self; this.args = args; this.argNames = argNames; this.retval = retval; + this.generator = generator }; Fn.prototype = extend(Obj.prototype, { constructor: Fn, toString: function(maxDepth) { - if (maxDepth) maxDepth--; - var str = "fn("; + if (maxDepth == null) maxDepth = 0; + var str = this.generator ? "fn*(" : "fn("; for (var i = 0; i < this.args.length; ++i) { if (i) str += ", "; var name = this.argNames[i]; if (name && name != "?") str += name + ": "; - str += toString(this.args[i].getType(), maxDepth, this); + str += maxDepth > -3 ? toString(this.args[i], maxDepth - 1, this) : "?"; } str += ")"; if (!this.retval.isEmpty()) - str += " -> " + toString(this.retval.getType(), maxDepth, this); + str += " -> " + (maxDepth > -3 ? toString(this.retval, maxDepth - 1, this) : "?"); return str; }, getProp: function(prop) { if (prop == "prototype") { var known = this.hasProp(prop, false); if (!known) { known = this.defProp(prop); var proto = new Obj(true, this.name && this.name + ".prototype"); @@ -587,27 +730,81 @@ return found; } return Obj.prototype.defProp.call(this, prop, originNode); }, getFunctionType: function() { return this; } }); var Arr = exports.Arr = function(contentType) { - Obj.call(this, cx.protos.Array); - var content = this.defProp("<i>"); - if (contentType) contentType.propagate(content); + Obj.call(this, cx.protos.Array) + var content = this.defProp("<i>") + if (Array.isArray(contentType)) { + this.tuple = contentType.length + for (var i = 0; i < contentType.length; i++) { + var prop = this.defProp(String(i)) + contentType[i].propagate(prop) + prop.propagate(content) + } + } else if (contentType) { + this.tuple = 0 + contentType.propagate(content) + } }; Arr.prototype = extend(Obj.prototype, { constructor: Arr, toString: function(maxDepth) { - return "[" + toString(this.getProp("<i>").getType(), maxDepth, this) + "]"; + if (maxDepth == null) maxDepth = 0 + if (maxDepth <= -3) return "[?]" + var content = "" + if (this.tuple) { + var similar + for (var i = 0; i in this.props; i++) { + var type = toString(this.getProp(String(i)), maxDepth - 1, this) + if (similar == null) + similar = type + else if (similar != type) + similar = false + else + similar = type + content += (content ? ", " : "") + type + } + if (similar) content = similar + } else { + content = toString(this.getProp("<i>"), maxDepth - 1, this) + } + return "[" + content + "]" + }, + normalizeIntegerProp: function(prop) { + if (+prop < this.tuple) return prop + else return "<i>" } }); + var Sym = exports.Sym = function(name, originNode) { + Prim.call(this, cx.protos.Symbol, "Symbol") + this.symName = name + this.originNode = originNode + } + Sym.prototype = extend(Prim.prototype, { + constructor: Sym, + asPropName: function() { return ":" + this.symName }, + getSymbolType: function() { return this } + }) + + exports.getSymbol = function(name, originNode) { + var cleanName = name.replace(/[^\w$\.]/g, "_") + var known = cx.symbols[cleanName] + if (known) { + if (originNode && !known.originNode) known.originNode = originNode + return known + } + return cx.symbols[cleanName] = new Sym(cleanName, originNode) + } + // THE PROPERTY REGISTRY function registerProp(prop, obj) { var data = cx.props[prop] || (cx.props[prop] = []); data.push(obj); } function objsWithProp(prop) { @@ -622,51 +819,59 @@ this.protos = Object.create(null); this.origins = []; this.curOrigin = "ecma5"; this.paths = Object.create(null); this.definitions = Object.create(null); this.purgeGen = 0; this.workList = null; this.disabledComputing = null; + this.curSuperCtor = this.curSuper = null; + this.symbols = Object.create(null) exports.withContext(this, function() { cx.protos.Object = new Obj(null, "Object.prototype"); cx.topScope = new Scope(); cx.topScope.name = "<top>"; cx.protos.Array = new Obj(true, "Array.prototype"); - cx.protos.Function = new Obj(true, "Function.prototype"); + cx.protos.Function = new Fn("Function.prototype", ANull, [], [], ANull); + cx.protos.Function.proto = cx.protos.Object; cx.protos.RegExp = new Obj(true, "RegExp.prototype"); cx.protos.String = new Obj(true, "String.prototype"); cx.protos.Number = new Obj(true, "Number.prototype"); cx.protos.Boolean = new Obj(true, "Boolean.prototype"); + cx.protos.Symbol = new Obj(true, "Symbol.prototype"); cx.str = new Prim(cx.protos.String, "string"); cx.bool = new Prim(cx.protos.Boolean, "bool"); cx.num = new Prim(cx.protos.Number, "number"); cx.curOrigin = null; if (defs) for (var i = 0; i < defs.length; ++i) def.load(defs[i]); }); }; + exports.Context.prototype.startAnalysis = function() { + this.disabledComputing = this.workList = this.curSuperCtor = this.curSuper = null; + }; + var cx = null; exports.cx = function() { return cx; }; exports.withContext = function(context, f) { var old = cx; cx = context; try { return f(); } finally { cx = old; } }; exports.TimedOut = function() { this.message = "Timed out"; this.stack = (new Error()).stack; - } + }; exports.TimedOut.prototype = Object.create(Error.prototype); exports.TimedOut.prototype.name = "infer.TimedOut"; var timeout; exports.withTimeout = function(ms, f) { var end = +new Date + ms; var oldEnd = timeout; if (oldEnd && oldEnd < end) return f(); @@ -674,121 +879,134 @@ try { return f(); } finally { timeout = oldEnd; } }; exports.addOrigin = function(origin) { if (cx.origins.indexOf(origin) < 0) cx.origins.push(origin); }; - var baseMaxWorkDepth = 20, reduceMaxWorkDepth = .0001; + var baseMaxWorkDepth = 20, reduceMaxWorkDepth = 0.0001; function withWorklist(f) { if (cx.workList) return f(cx.workList); var list = [], depth = 0; var add = cx.workList = function(type, target, weight) { if (depth < baseMaxWorkDepth - reduceMaxWorkDepth * list.length) list.push(type, target, weight, depth); }; - try { - var ret = f(add); - for (var i = 0; i < list.length; i += 4) { - if (timeout && +new Date >= timeout) - throw new exports.TimedOut(); - depth = list[i + 3] + 1; - list[i + 1].addType(list[i], list[i + 2]); - } - return ret; - } finally { - cx.workList = null; + var ret = f(add); + for (var i = 0; i < list.length; i += 4) { + if (timeout && +new Date >= timeout) + throw new exports.TimedOut(); + depth = list[i + 3] + 1; + list[i + 1].addType(list[i], list[i + 2]); } + cx.workList = null; + return ret; + } + + function withSuper(ctor, obj, f) { + var oldCtor = cx.curSuperCtor, oldObj = cx.curSuper + cx.curSuperCtor = ctor; cx.curSuper = obj + var result = f() + cx.curSuperCtor = oldCtor; cx.curSuper = oldObj + return result } // SCOPES - var Scope = exports.Scope = function(prev) { + var Scope = exports.Scope = function(prev, originNode, isBlock) { Obj.call(this, prev || true); this.prev = prev; + this.originNode = originNode + this.isBlock = !!isBlock }; Scope.prototype = extend(Obj.prototype, { constructor: Scope, defVar: function(name, originNode) { for (var s = this; ; s = s.proto) { var found = s.props[name]; if (found) return found; if (!s.prev) return s.defProp(name, originNode); } } }); + function functionScope(scope) { + while (scope.isBlock) scope = scope.prev + return scope + } + + // RETVAL COMPUTATION HEURISTICS function maybeInstantiate(scope, score) { - if (scope.fnType) - scope.fnType.instantiateScore = (scope.fnType.instantiateScore || 0) + score; + var fn = functionScope(scope).fnType + if (fn) fn.instantiateScore = (fn.instantiateScore || 0) + score; } var NotSmaller = {}; function nodeSmallerThan(node, n) { try { walk.simple(node, {Expression: function() { if (--n <= 0) throw NotSmaller; }}); return true; } catch(e) { if (e == NotSmaller) return false; throw e; } } - function maybeTagAsInstantiated(node, scope) { - var score = scope.fnType.instantiateScore; - if (!cx.disabledComputing && score && scope.fnType.args.length && nodeSmallerThan(node, score * 5)) { - maybeInstantiate(scope.prev, score / 2); - setFunctionInstantiated(node, scope); + function maybeTagAsInstantiated(node, fn) { + var score = fn.instantiateScore; + if (!cx.disabledComputing && score && fn.args.length && nodeSmallerThan(node, score * 5)) { + maybeInstantiate(functionScope(fn.originNode.scope.prev), score / 2); + setFunctionInstantiated(node, fn); return true; } else { - scope.fnType.instantiateScore = null; + fn.instantiateScore = null; } } - function setFunctionInstantiated(node, scope) { - var fn = scope.fnType; + function setFunctionInstantiated(node, fn) { // Disconnect the arg avals, so that we can add info to them without side effects for (var i = 0; i < fn.args.length; ++i) fn.args[i] = new AVal; fn.self = new AVal; fn.computeRet = function(self, args) { // Prevent recursion return withDisabledComputing(fn, function() { var oldOrigin = cx.curOrigin; cx.curOrigin = fn.origin; - var scopeCopy = new Scope(scope.prev); - scopeCopy.originNode = scope.originNode; + var scope = node.scope + var scopeCopy = new Scope(scope.prev, scope.originNode); for (var v in scope.props) { var local = scopeCopy.defProp(v, scope.props[v].originNode); for (var i = 0; i < args.length; ++i) if (fn.argNames[i] == v && i < args.length) args[i].propagate(local); } var argNames = fn.argNames.length != args.length ? fn.argNames.slice(0, args.length) : fn.argNames; while (argNames.length < args.length) argNames.push("?"); - scopeCopy.fnType = new Fn(fn.name, self, args, argNames, ANull); + scopeCopy.fnType = new Fn(fn.name, self, args, argNames, ANull, fn.generator); + scopeCopy.fnType.originNode = fn.originNode; if (fn.arguments) { var argset = scopeCopy.fnType.arguments = new AVal; scopeCopy.defProp("arguments").addType(new Arr(argset)); for (var i = 0; i < args.length; ++i) args[i].propagate(argset); } - node.body.scope = scopeCopy; + node.scope = scopeCopy; walk.recursive(node.body, scopeCopy, null, scopeGatherer); walk.recursive(node.body, scopeCopy, null, inferWrapper); cx.curOrigin = oldOrigin; return scopeCopy.fnType.retval; }); }; } - function maybeTagAsGeneric(scope) { - var fn = scope.fnType, target = fn.retval; + function maybeTagAsGeneric(fn) { + var target = fn.retval; if (target == ANull) return; var targetInner, asArray; if (!target.isEmpty() && (targetInner = target.getType()) instanceof Arr) target = asArray = targetInner.getProp("<i>"); function explore(aval, path, depth) { if (depth > 3 || !aval.forward) return; for (var i = 0; i < aval.forward.length; ++i) { @@ -809,383 +1027,701 @@ var foundPath = explore(fn.self, "!this", 0); for (var i = 0; !foundPath && i < fn.args.length; ++i) foundPath = explore(fn.args[i], "!" + i, 0); if (foundPath) { if (asArray) foundPath = "[" + foundPath + "]"; var p = new def.TypeParser(foundPath); - fn.computeRet = p.parseRetType(); + var parsed = p.parseType(true); + fn.computeRet = parsed.apply ? parsed : function() { return parsed; }; fn.computeRetSource = foundPath; return true; } } // SCOPE GATHERING PASS function addVar(scope, nameNode) { return scope.defProp(nameNode.name, nameNode); } + function patternName(node) { + if (node.type == "Identifier") return node.name + if (node.type == "AssignmentPattern") return patternName(node.left) + if (node.type == "ObjectPattern") return "{" + node.properties.map(function(e) { return patternName(e.value) }).join(", ") + "}" + if (node.type == "ArrayPattern") return "[" + node.elements.map(patternName).join(", ") + "]" + if (node.type == "RestElement") return "..." + patternName(node.argument) + return "_" + } - var scopeGatherer = walk.make({ + function isBlockScopedDecl(node) { + return node.type == "VariableDeclaration" && node.kind != "var" || + node.type == "FunctionDeclaration" || + node.type == "ClassDeclaration"; + } + + function patternScopes(inner, outer) { + return {inner: inner, outer: outer || inner} + } + + var scopeGatherer = exports.scopeGatherer = walk.make({ + VariablePattern: function(node, scopes) { + if (scopes.inner) addVar(scopes.inner, node) + }, + AssignmentPattern: function(node, scopes, c) { + c(node.left, scopes, "Pattern") + c(node.right, scopes.outer, "Expression") + }, + AssignmentExpression: function(node, scope, c) { + if (node.left.type == "MemberExpression") + c(node.left, scope, "Expression") + else + c(node.left, patternScopes(false, scope), "Pattern") + c(node.right, scope, "Expression") + }, Function: function(node, scope, c) { - var inner = node.body.scope = new Scope(scope); - inner.originNode = node; - var argVals = [], argNames = []; + var inner = node.scope = new Scope(scope, node) + var argVals = [], argNames = [] for (var i = 0; i < node.params.length; ++i) { - var param = node.params[i]; - argNames.push(param.name); - argVals.push(addVar(inner, param)); + var param = node.params[i] + argNames.push(patternName(param)) + if (param.type == "Identifier") { + argVals.push(addVar(inner, param)) + } else { + var arg = new AVal + argVals.push(arg) + arg.originNode = param + c(param, patternScopes(inner), "Pattern") + } } - inner.fnType = new Fn(node.id && node.id.name, new AVal, argVals, argNames, ANull); + inner.fnType = new Fn(node.id && node.id.name, new AVal, argVals, argNames, ANull, node.generator) inner.fnType.originNode = node; if (node.id) { var decl = node.type == "FunctionDeclaration"; addVar(decl ? scope : inner, node.id); } - c(node.body, inner, "ScopeBody"); + c(node.body, inner, node.expression ? "Expression" : "Statement"); + }, + BlockStatement: function(node, scope, c) { + if (!node.scope && node.body.some(isBlockScopedDecl)) + scope = node.scope = new Scope(scope, node, true) + walk.base.BlockStatement(node, scope, c) }, TryStatement: function(node, scope, c) { c(node.block, scope, "Statement"); if (node.handler) { - var v = addVar(scope, node.handler.param); - c(node.handler.body, scope, "ScopeBody"); - var e5 = cx.definitions.ecma5; - if (e5 && v.isEmpty()) getInstance(e5["Error.prototype"]).propagate(v, WG_CATCH_ERROR); + if (node.handler.param.type == "Identifier") { + var v = addVar(scope, node.handler.param); + c(node.handler.body, scope, "Statement"); + var e5 = cx.definitions.ecma5; + if (e5 && v.isEmpty()) getInstance(e5["Error.prototype"]).propagate(v, WG_CATCH_ERROR); + } else { + c(node.handler.param, patternScopes(scope), "Pattern") + } } if (node.finalizer) c(node.finalizer, scope, "Statement"); }, VariableDeclaration: function(node, scope, c) { + var targetScope = node.kind == "var" ? functionScope(scope) : scope for (var i = 0; i < node.declarations.length; ++i) { var decl = node.declarations[i]; - addVar(scope, decl.id); + c(decl.id, patternScopes(targetScope, scope), "Pattern") if (decl.init) c(decl.init, scope, "Expression"); } + }, + ClassDeclaration: function(node, scope, c) { + addVar(scope, node.id) + if (node.superClass) c(node.superClass, scope, "Expression") + for (var i = 0; i < node.body.body.length; i++) + c(node.body.body[i], scope) + }, + ForInStatement: function(node, scope, c) { + if (!node.scope && isBlockScopedDecl(node.left)) + scope = node.scope = new Scope(scope, node, true) + walk.base.ForInStatement(node, scope, c) + }, + ForStatement: function(node, scope, c) { + if (!node.scope && node.init && isBlockScopedDecl(node.init)) + scope = node.scope = new Scope(scope, node, true) + walk.base.ForStatement(node, scope, c) + }, + ImportDeclaration: function(node, scope) { + for (var i = 0; i < node.specifiers.length; i++) + addVar(scope, node.specifiers[i].local) } }); + scopeGatherer.ForOfStatement = scopeGatherer.ForInStatement // CONSTRAINT GATHERING PASS - function propName(node, scope, c) { - var prop = node.property; - if (!node.computed) return prop.name; - if (prop.type == "Literal" && typeof prop.value == "string") return prop.value; - if (c) infer(prop, scope, c, ANull); + var propName = exports.propName = function(node, inferInScope) { + var key = node.property || node.key; + if (!node.computed && key.type == "Identifier") return key.name; + if (key.type == "Literal") { + if (typeof key.value == "string") return key.value + if (typeof key.value == "number") return String(key.value) + } + if (inferInScope) { + var symName = symbolName(infer(key, inferInScope)) + if (symName) return node.propName = symName + } else if (node.propName) { + return node.propName + } return "<i>"; } + function symbolName(val) { + var sym = val.getSymbolType() + if (sym) return sym.asPropName() + } function unopResultType(op) { switch (op) { case "+": case "-": case "~": return cx.num; case "!": return cx.bool; case "typeof": return cx.str; case "void": case "delete": return ANull; } } function binopIsBoolean(op) { switch (op) { case "==": case "!=": case "===": case "!==": case "<": case ">": case ">=": case "<=": case "in": case "instanceof": return true; } } - function literalType(val) { - switch (typeof val) { + function literalType(node) { + if (node.regex) return getInstance(cx.protos.RegExp); + switch (typeof node.value) { case "boolean": return cx.bool; case "number": return cx.num; case "string": return cx.str; case "object": case "function": - if (!val) return ANull; + if (!node.value) return ANull; return getInstance(cx.protos.RegExp); } } + function join(a, b) { + if (a == b || b == ANull) return a + if (a == ANull) return b + var joined = new AVal + a.propagate(joined) + b.propagate(joined) + return joined + } + + function connectParams(node, scope) { + for (var i = 0; i < node.params.length; i++) { + var param = node.params[i] + if (param.type == "Identifier") continue + connectPattern(param, scope, node.scope.fnType.args[i]) + } + } + + function ensureVar(node, scope) { + return scope.hasProp(node.name) || cx.topScope.defProp(node.name, node) + } + + var inferPatternVisitor = exports.inferPatternVisitor = { + Identifier: function(node, scope, source) { + source.propagate(ensureVar(node, scope)) + }, + MemberExpression: function(node, scope, source) { + var obj = infer(node.object, scope) + var pName = propName(node, scope) + obj.propagate(new DefProp(pName, source, node.property)) + }, + RestElement: function(node, scope, source) { + connectPattern(node.argument, scope, new Arr(source)) + }, + ObjectPattern: function(node, scope, source) { + for (var i = 0; i < node.properties.length; ++i) { + var prop = node.properties[i] + connectPattern(prop.value, scope, source.getProp(prop.key.name)) + } + }, + ArrayPattern: function(node, scope, source) { + for (var i = 0; i < node.elements.length; i++) + if (node.elements[i]) + connectPattern(node.elements[i], scope, source.getProp(String(i))) + }, + AssignmentPattern: function(node, scope, source) { + connectPattern(node.left, scope, join(source, infer(node.right, scope))) + } + } + + function connectPattern(node, scope, source) { + var connecter = inferPatternVisitor[node.type] + if (connecter) connecter(node, scope, source) + } + + function getThis(scope) { + var fnScope = functionScope(scope) + return fnScope.fnType ? fnScope.fnType.self : fnScope + } + + function maybeAddPhantomObj(obj) { + if (!obj.isEmpty() || !obj.propertyOf) return + obj.propertyOf.getProp(obj.propertyName).addType(new Obj, WG_PHANTOM_OBJ) + maybeAddPhantomObj(obj.propertyOf) + } + + function inferClass(node, scope, name) { + if (!name && node.id) name = node.id.name + + var sup = cx.protos.Object, supCtor, delayed + if (node.superClass) { + if (node.superClass.type == "Literal" && node.superClass.value == null) { + sup = null + } else { + var supVal = infer(node.superClass, scope), supProto + supCtor = supVal.getFunctionType() + if (supCtor && (supProto = supCtor.getProp("prototype").getObjType())) { + sup = supProto + } else { + supCtor = supVal + delayed = supVal.getProp("prototype") + } + } + } + var proto = new Obj(sup, name && name + ".prototype") + if (delayed) delayed.propagate(new HasProto(proto)) + + return withSuper(supCtor, delayed || sup, function() { + var ctor, body = node.body.body + for (var i = 0; i < body.length; i++) + if (body[i].kind == "constructor") ctor = body[i].value + var fn = node.objType = ctor ? infer(ctor, scope) : new Fn(name, ANull, [], null, ANull) + fn.originNode = node.id || ctor || node + + var inst = getInstance(proto, fn) + fn.self.addType(inst) + fn.defProp("prototype", node).addType(proto) + for (var i = 0; i < body.length; i++) { + var method = body[i], target + if (method.kind == "constructor") continue + var pName = propName(method, scope) + if (pName == "<i>" || method.kind == "set") { + target = ANull + } else { + target = (method.static ? fn : proto).defProp(pName, method.key) + target.initializer = true + if (method.kind == "get") target = new IsCallee(inst, [], null, target) + } + infer(method.value, scope, target) + var methodFn = target.getFunctionType() + if (methodFn) methodFn.self.addType(inst) + } + return fn + }) + } + + function arrayLiteralType(elements, scope, inner) { + var tuple = elements.length > 1 && elements.length < 6 + if (tuple) { + var homogenous = true, litType + for (var i = 0; i < elements.length; i++) { + var elt = elements[i] + if (!elt) + tuple = false + else if (elt.type != "Literal" || (litType && litType != typeof elt.value)) + homogenous = false + else + litType = typeof elt.value + } + if (homogenous) tuple = false + } + + if (tuple) { + var types = [] + for (var i = 0; i < elements.length; ++i) + types.push(inner(elements[i], scope)) + return new Arr(types) + } else if (elements.length < 2) { + return new Arr(elements[0] && inner(elements[0], scope)) + } else { + var eltVal = new AVal + for (var i = 0; i < elements.length; i++) + if (elements[i]) inner(elements[i], scope).propagate(eltVal) + return new Arr(eltVal) + } + } + function ret(f) { - return function(node, scope, c, out, name) { - var r = f(node, scope, c, name); + return function(node, scope, out, name) { + var r = f(node, scope, name); if (out) r.propagate(out); return r; }; } function fill(f) { - return function(node, scope, c, out, name) { + return function(node, scope, out, name) { if (!out) out = new AVal; - f(node, scope, c, out, name); + f(node, scope, out, name); return out; }; } - var inferExprVisitor = { - ArrayExpression: ret(function(node, scope, c) { - var eltval = new AVal; - for (var i = 0; i < node.elements.length; ++i) { - var elt = node.elements[i]; - if (elt) infer(elt, scope, c, eltval); + var inferExprVisitor = exports.inferExprVisitor = { + ArrayExpression: ret(function(node, scope) { + return arrayLiteralType(node.elements, scope, infer) + }), + ObjectExpression: ret(function(node, scope, name) { + var proto = true, waitForProto + for (var i = 0; i < node.properties.length; ++i) { + var prop = node.properties[i] + if (prop.key.name == "__proto__") { + if (prop.value.type == "Literal" && prop.value.value == null) { + proto = null + } else { + var protoVal = infer(prop.value, scope), known = protoVal.getObjType() + if (known) proto = known + else waitForProto = protoVal + } + } } - return new Arr(eltval); - }), - ObjectExpression: ret(function(node, scope, c, name) { - var obj = node.objType = new Obj(true, name); + + var obj = node.objType = new Obj(proto, name); + if (waitForProto) waitForProto.propagate(new HasProto(obj)) obj.originNode = node; - for (var i = 0; i < node.properties.length; ++i) { - var prop = node.properties[i], key = prop.key, name; - if (key.type == "Identifier") { - name = key.name; - } else if (typeof key.value == "string") { - name = key.value; - } else { - infer(prop.value, scope, c, ANull); - continue; + withSuper(null, waitForProto || proto, function() { + for (var i = 0; i < node.properties.length; ++i) { + var prop = node.properties[i], key = prop.key; + if (prop.value.name == "✖" || prop.key.name == "__proto__") continue; + + var name = propName(prop, scope), target + if (name == "<i>" || prop.kind == "set") { + target = ANull; + } else { + var val = target = obj.defProp(name, key); + val.initializer = true; + if (prop.kind == "get") + target = new IsCallee(obj, [], null, val); + } + infer(prop.value, scope, target, name); + if (prop.value.type == "FunctionExpression") + prop.value.scope.fnType.self.addType(obj, WG_SPECULATIVE_THIS); } - var val = obj.defProp(name, key); - val.initializer = true; - infer(prop.value, scope, c, val, name); - } + }) return obj; }), - FunctionExpression: ret(function(node, scope, c, name) { - var inner = node.body.scope, fn = inner.fnType; + FunctionExpression: ret(function(node, scope, name) { + var inner = node.scope, fn = inner.fnType; if (name && !fn.name) fn.name = name; - c(node.body, scope, "ScopeBody"); - maybeTagAsInstantiated(node, inner) || maybeTagAsGeneric(inner); + connectParams(node, inner) + if (node.expression) + infer(node.body, inner, inner.fnType.retval = new AVal) + else + walk.recursive(node.body, inner, null, inferWrapper, "Statement") + if (node.type == "ArrowFunctionExpression") { + getThis(scope).propagate(fn.self) + fn.self = ANull + } + maybeTagAsInstantiated(node, fn) || maybeTagAsGeneric(fn); if (node.id) inner.getProp(node.id.name).addType(fn); return fn; }), - SequenceExpression: ret(function(node, scope, c) { + ClassExpression: ret(inferClass), + SequenceExpression: ret(function(node, scope) { for (var i = 0, l = node.expressions.length - 1; i < l; ++i) - infer(node.expressions[i], scope, c, ANull); - return infer(node.expressions[l], scope, c); + infer(node.expressions[i], scope, ANull); + return infer(node.expressions[l], scope); }), - UnaryExpression: ret(function(node, scope, c) { - infer(node.argument, scope, c, ANull); + UnaryExpression: ret(function(node, scope) { + infer(node.argument, scope, ANull); return unopResultType(node.operator); }), - UpdateExpression: ret(function(node, scope, c) { - infer(node.argument, scope, c, ANull); + UpdateExpression: ret(function(node, scope) { + infer(node.argument, scope, ANull); return cx.num; }), - BinaryExpression: ret(function(node, scope, c) { + BinaryExpression: ret(function(node, scope) { if (node.operator == "+") { - var lhs = infer(node.left, scope, c); - var rhs = infer(node.right, scope, c); + var lhs = infer(node.left, scope); + var rhs = infer(node.right, scope); if (lhs.hasType(cx.str) || rhs.hasType(cx.str)) return cx.str; if (lhs.hasType(cx.num) && rhs.hasType(cx.num)) return cx.num; var result = new AVal; lhs.propagate(new IsAdded(rhs, result)); rhs.propagate(new IsAdded(lhs, result)); return result; } else { - infer(node.left, scope, c, ANull); - infer(node.right, scope, c, ANull); + infer(node.left, scope, ANull); + infer(node.right, scope, ANull); return binopIsBoolean(node.operator) ? cx.bool : cx.num; } }), - AssignmentExpression: ret(function(node, scope, c) { - var rhs, name, pName; + AssignmentExpression: ret(function(node, scope, name) { + var rhs, pName; if (node.left.type == "MemberExpression") { - pName = propName(node.left, scope, c); - if (node.left.object.type == "Identifier") - name = node.left.object.name + "." + pName; - } else { - name = node.left.name; + pName = propName(node.left, scope) + if (!name) + name = node.left.object.type == "Identifier" ? node.left.object.name + "." + pName : pName + } else if (!name && node.left.type == "Identifier") { + name = node.left.name } - if (node.operator != "=" && node.operator != "+=") { - infer(node.right, scope, c, ANull); + if (node.operator && node.operator != "=" && node.operator != "+=") { + infer(node.right, scope, ANull); rhs = cx.num; } else { - rhs = infer(node.right, scope, c, null, name); + rhs = infer(node.right, scope, null, name); } if (node.left.type == "MemberExpression") { - var obj = infer(node.left.object, scope, c); + var obj = infer(node.left.object, scope); if (pName == "prototype") maybeInstantiate(scope, 20); if (pName == "<i>") { // This is a hack to recognize for/in loops that copy // properties, and do the copying ourselves, insofar as we // manage, because such loops tend to be relevant for type // information. var v = node.left.property.name, local = scope.props[v], over = local && local.iteratesOver; if (over) { maybeInstantiate(scope, 20); var fromRight = node.right.type == "MemberExpression" && node.right.computed && node.right.property.name == v; over.forAllProps(function(prop, val, local) { if (local && prop != "prototype" && prop != "<i>") - obj.propagate(new PropHasSubset(prop, fromRight ? val : ANull)); + obj.propagate(new DefProp(prop, fromRight ? val : ANull)); }); return rhs; } } - obj.propagate(new PropHasSubset(pName, rhs, node.left.property)); - } else { // Identifier - var v = scope.defVar(node.left.name, node.left); - if (v.maybePurge) v.maybePurge = false; - rhs.propagate(v); + + obj.propagate(new DefProp(pName, rhs, node.left.property)); + maybeAddPhantomObj(obj) + if (node.right.type == "FunctionExpression") + obj.propagate(node.right.scope.fnType.self, WG_SPECULATIVE_THIS); + } else { + connectPattern(node.left, scope, rhs) } return rhs; }), - LogicalExpression: fill(function(node, scope, c, out) { - infer(node.left, scope, c, out); - infer(node.right, scope, c, out); + LogicalExpression: fill(function(node, scope, out) { + infer(node.left, scope, out); + infer(node.right, scope, out); }), - ConditionalExpression: fill(function(node, scope, c, out) { - infer(node.test, scope, c, ANull); - infer(node.consequent, scope, c, out); - infer(node.alternate, scope, c, out); + ConditionalExpression: fill(function(node, scope, out) { + infer(node.test, scope, ANull); + infer(node.consequent, scope, out); + infer(node.alternate, scope, out); }), - NewExpression: fill(function(node, scope, c, out, name) { + NewExpression: fill(function(node, scope, out, name) { if (node.callee.type == "Identifier" && node.callee.name in scope.props) maybeInstantiate(scope, 20); for (var i = 0, args = []; i < node.arguments.length; ++i) - args.push(infer(node.arguments[i], scope, c)); - var callee = infer(node.callee, scope, c); + args.push(infer(node.arguments[i], scope)); + var callee = infer(node.callee, scope); var self = new AVal; callee.propagate(new IsCtor(self, name && /\.prototype$/.test(name))); self.propagate(out, WG_NEW_INSTANCE); callee.propagate(new IsCallee(self, args, node.arguments, new IfObj(out))); }), - CallExpression: fill(function(node, scope, c, out) { + CallExpression: fill(function(node, scope, out) { for (var i = 0, args = []; i < node.arguments.length; ++i) - args.push(infer(node.arguments[i], scope, c)); + args.push(infer(node.arguments[i], scope)); + var outerFn = functionScope(scope).fnType if (node.callee.type == "MemberExpression") { - var self = infer(node.callee.object, scope, c); - var pName = propName(node.callee, scope, c); - if ((pName == "call" || pName == "apply") && - scope.fnType && scope.fnType.args.indexOf(self) > -1) + var self = infer(node.callee.object, scope); + var pName = propName(node.callee, scope) + if (outerFn && (pName == "call" || pName == "apply") && + outerFn.args.indexOf(self) > -1) maybeInstantiate(scope, 30); self.propagate(new HasMethodCall(pName, args, node.arguments, out)); + } else if (node.callee.type == "Super" && cx.curSuperCtor) { + cx.curSuperCtor.propagate(new IsCallee(getThis(scope), args, node.arguments, out)) } else { - var callee = infer(node.callee, scope, c); - if (scope.fnType && scope.fnType.args.indexOf(callee) > -1) + var callee = infer(node.callee, scope); + if (outerFn && outerFn.args.indexOf(callee) > -1) maybeInstantiate(scope, 30); var knownFn = callee.getFunctionType(); - if (knownFn && knownFn.instantiateScore && scope.fnType) + if (knownFn && knownFn.instantiateScore && outerFn) maybeInstantiate(scope, knownFn.instantiateScore / 5); callee.propagate(new IsCallee(cx.topScope, args, node.arguments, out)); } }), - MemberExpression: fill(function(node, scope, c, out) { - var name = propName(node, scope); - var obj = infer(node.object, scope, c); - var prop = obj.getProp(name); + MemberExpression: fill(function(node, scope, out) { + var name = propName(node), wg; if (name == "<i>") { - var propType = infer(node.property, scope, c); - if (!propType.hasType(cx.num)) - return prop.propagate(out, WG_MULTI_MEMBER); + var propType = infer(node.property, scope) + var symName = symbolName(propType) + if (symName) + name = node.propName = symName + else if (!propType.hasType(cx.num)) + wg = WG_MULTI_MEMBER } - prop.propagate(out); + infer(node.object, scope).getProp(name).propagate(out, wg) }), Identifier: ret(function(node, scope) { - if (node.name == "arguments" && scope.fnType && !(node.name in scope.props)) - scope.defProp(node.name, scope.fnType.originNode) - .addType(new Arr(scope.fnType.arguments = new AVal)); + if (node.name == "arguments") { + var fnScope = functionScope(scope) + if (fnScope.fnType && !(node.name in fnScope.props)) + scope.defProp(node.name, fnScope.fnType.originNode) + .addType(new Arr(fnScope.fnType.arguments = new AVal)); + } return scope.getProp(node.name); }), ThisExpression: ret(function(_node, scope) { - return scope.fnType ? scope.fnType.self : cx.topScope; + return getThis(scope) + }), + Super: ret(function(node) { + return node.superType = cx.curSuper || ANull }), Literal: ret(function(node) { - return literalType(node.value); + return literalType(node); + }), + TemplateLiteral: ret(function(node, scope) { + for (var i = 0; i < node.expressions.length; ++i) + infer(node.expressions[i], scope, ANull) + return cx.str + }), + TaggedTemplateExpression: fill(function(node, scope, out) { + var args = [new Arr(cx.str)] + for (var i = 0; i < node.quasi.expressions.length; ++i) + args.push(infer(node.quasi.expressions[i], scope)) + infer(node.tag, scope, new IsCallee(cx.topScope, args, node.quasi.expressions, out)) + }), + YieldExpression: ret(function(node, scope) { + var output = ANull, fn = functionScope(scope).fnType + if (fn) { + if (fn.retval == ANull) fn.retval = new AVal + if (!fn.yieldval) fn.yieldval = new AVal + output = fn.retval + } + if (node.argument) { + if (node.delegate) { + infer(node.argument, scope, new HasMethodCall("next", [], null, + new GetProp("value", output))) + } else { + infer(node.argument, scope, output) + } + } + return fn ? fn.yieldval : ANull }) }; + inferExprVisitor.ArrowFunctionExpression = inferExprVisitor.FunctionExpression - function infer(node, scope, c, out, name) { - return inferExprVisitor[node.type](node, scope, c, out, name); + function infer(node, scope, out, name) { + var handler = inferExprVisitor[node.type]; + return handler ? handler(node, scope, out, name) : ANull; } - var inferWrapper = walk.make({ - Expression: function(node, scope, c) { - infer(node, scope, c, ANull); + function loopPattern(init) { + return init.type == "VariableDeclaration" ? init.declarations[0].id : init + } + + var inferWrapper = exports.inferWrapper = walk.make({ + Expression: function(node, scope) { + infer(node, node.scope || scope, ANull); }, FunctionDeclaration: function(node, scope, c) { - var inner = node.body.scope, fn = inner.fnType; - c(node.body, scope, "ScopeBody"); - maybeTagAsInstantiated(node, inner) || maybeTagAsGeneric(inner); - var prop = scope.getProp(node.id.name); - prop.addType(fn); + var inner = node.scope, fn = inner.fnType; + connectParams(node, inner) + c(node.body, inner, "Statement"); + maybeTagAsInstantiated(node, fn) || maybeTagAsGeneric(fn); + scope.getProp(node.id.name).addType(fn) + }, + + Statement: function(node, scope, c) { + c(node, node.scope || scope) }, - VariableDeclaration: function(node, scope, c) { + VariableDeclaration: function(node, scope) { for (var i = 0; i < node.declarations.length; ++i) { - var decl = node.declarations[i], prop = scope.getProp(decl.id.name); - if (decl.init) - infer(decl.init, scope, c, prop, decl.id.name); + var decl = node.declarations[i]; + if (decl.id.type == "Identifier") { + var prop = scope.getProp(decl.id.name); + if (decl.init) + infer(decl.init, scope, prop, decl.id.name); + } else if (decl.init) { + connectPattern(decl.id, scope, infer(decl.init, scope)) + } } }, - ReturnStatement: function(node, scope, c) { + ClassDeclaration: function(node, scope) { + scope.getProp(node.id.name).addType(inferClass(node, scope, node.id.name)) + }, + + ReturnStatement: function(node, scope) { if (!node.argument) return; - var output = ANull; - if (scope.fnType) { - if (scope.fnType.retval == ANull) scope.fnType.retval = new AVal; - output = scope.fnType.retval; + var output = ANull, fn = functionScope(scope).fnType + if (fn) { + if (fn.retval == ANull) fn.retval = new AVal; + output = fn.retval; } - infer(node.argument, scope, c, output); + infer(node.argument, scope, output); }, ForInStatement: function(node, scope, c) { - var source = infer(node.right, scope, c); + var source = infer(node.right, scope); if ((node.right.type == "Identifier" && node.right.name in scope.props) || (node.right.type == "MemberExpression" && node.right.property.name == "prototype")) { maybeInstantiate(scope, 5); - var varName; - if (node.left.type == "Identifier") { - varName = node.left.name; - } else if (node.left.type == "VariableDeclaration") { - varName = node.left.declarations[0].id.name; + var pattern = loopPattern(node.left) + if (pattern.type == "Identifier") { + if (pattern.name in scope.props) + scope.getProp(pattern.name).iteratesOver = source + source.getProp("<i>").propagate(ensureVar(pattern, scope)) + } else { + connectPattern(pattern, scope, source.getProp("<i>")) } - if (varName && varName in scope.props) - scope.getProp(varName).iteratesOver = source; } c(node.body, scope, "Statement"); }, - ScopeBody: function(node, scope, c) { c(node, node.scope || scope); } + ForOfStatement: function(node, scope, c) { + var pattern = loopPattern(node.left), target + if (pattern.type == "Identifier") + target = ensureVar(pattern, scope) + else + connectPattern(pattern, scope, target = new AVal) + infer(node.right, scope, new HasMethodCall(":Symbol.iterator", [], null, + new HasMethodCall("next", [], null, + new GetProp("value", target)))) + c(node.body, scope, "Statement") + } }); // PARSING - function runPasses(passes, pass) { - var arr = passes && passes[pass]; - var args = Array.prototype.slice.call(arguments, 2); - if (arr) for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args); - } - - var parse = exports.parse = function(text, passes, options) { + var parse = exports.parse = function(text, options, thirdArg) { + if (!options || Array.isArray(options)) options = thirdArg var ast; try { ast = acorn.parse(text, options); } catch(e) { ast = acorn_loose.parse_dammit(text, options); } - runPasses(passes, "postParse", ast, text); return ast; }; // ANALYSIS INTERFACE - exports.analyze = function(ast, name, scope, passes) { + exports.analyze = function(ast, name, scope) { if (typeof ast == "string") ast = parse(ast); if (!name) name = "file#" + cx.origins.length; exports.addOrigin(cx.curOrigin = name); if (!scope) scope = cx.topScope; + cx.startAnalysis(); + walk.recursive(ast, scope, null, scopeGatherer); - runPasses(passes, "preInfer", ast, scope); + if (cx.parent) cx.parent.signal("preInfer", ast, scope) walk.recursive(ast, scope, null, inferWrapper); - runPasses(passes, "postInfer", ast, scope); + if (cx.parent) cx.parent.signal("postInfer", ast, scope) cx.curOrigin = null; }; // PURGING - exports.purgeTypes = function(origins, start, end) { + exports.purge = function(origins, start, end) { var test = makePredicate(origins, start, end); ++cx.purgeGen; cx.topScope.purge(test); for (var prop in cx.props) { var list = cx.props[prop]; for (var i = 0; i < list.length; ++i) { var obj = list[i], av = obj.props[prop]; if (!av || test(av, av.originNode)) list.splice(i--, 1); @@ -1211,87 +1747,107 @@ this.purgeGen = cx.purgeGen; for (var i = 0; i < this.types.length; ++i) { var type = this.types[i]; if (test(type, type.originNode)) this.types.splice(i--, 1); else type.purge(test); } + if (!this.types.length) this.maxWeight = 0; + if (this.forward) for (var i = 0; i < this.forward.length; ++i) { var f = this.forward[i]; if (test(f)) { this.forward.splice(i--, 1); if (this.props) this.props = null; } else if (f.purge) { f.purge(test); } } }; ANull.purge = function() {}; Obj.prototype.purge = function(test) { if (this.purgeGen == cx.purgeGen) return true; this.purgeGen = cx.purgeGen; - var props = []; for (var p in this.props) { var av = this.props[p]; if (test(av, av.originNode)) this.removeProp(p); av.purge(test); } }; Fn.prototype.purge = function(test) { if (Obj.prototype.purge.call(this, test)) return; this.self.purge(test); this.retval.purge(test); for (var i = 0; i < this.args.length; ++i) this.args[i].purge(test); }; - exports.markVariablesDefinedBy = function(scope, origins, start, end) { - var test = makePredicate(origins, start, end); - for (var s = scope; s; s = s.prev) for (var p in s.props) { - var prop = s.props[p]; - if (test(prop, prop.originNode)) { - prop.maybePurge = true; - if (start == null && prop.originNode) prop.originNode = null; - } - } - }; - - exports.purgeMarkedVariables = function(scope) { - for (var s = scope; s; s = s.prev) for (var p in s.props) - if (s.props[p].maybePurge) delete s.props[p]; - }; - // EXPRESSION TYPE DETERMINATION function findByPropertyName(name) { guessing = true; var found = objsWithProp(name); if (found) for (var i = 0; i < found.length; ++i) { var val = found[i].getProp(name); if (!val.isEmpty()) return val; } return ANull; } - var typeFinder = { + function generatorResult(input, output) { + var retObj = new Obj(true) + retObj.defProp("done").addType(cx.bool) + output.propagate(retObj.defProp("value")) + var method = new Fn(null, ANull, input ? [input] : [], input ? ["?"] : [], retObj) + var result = new Obj(cx.definitions.ecma6 && cx.definitions.ecma6.generator_prototype || true) + result.defProp("next").addType(method) + return result + } + + function maybeIterator(fn, output) { + if (!fn.generator) return output + if (!fn.computeRet) { // Reuse iterator objects for non-computed return types + if (fn.generator === true) fn.generator = generatorResult(fn.yieldval, output) + return fn.generator + } + return generatorResult(fn.yieldval, output) + } + + function computeReturnType(funcNode, argNodes, scope) { + var fn = findType(funcNode, scope).getFunctionType() + if (!fn) return ANull + var result = fn.retval + if (fn.computeRet) { + for (var i = 0, args = []; i < argNodes.length; ++i) + args.push(findType(argNodes[i], scope)) + var self = ANull + if (funcNode.type == "MemberExpression") + self = findType(funcNode.object, scope) + result = fn.computeRet(self, args, argNodes); + } + return maybeIterator(fn, result) + } + + var typeFinder = exports.typeFinder = { ArrayExpression: function(node, scope) { - var eltval = new AVal; - for (var i = 0; i < node.elements.length; ++i) { - var elt = node.elements[i]; - if (elt) findType(elt, scope).propagate(eltval); - } - return new Arr(eltval); + return arrayLiteralType(node.elements, scope, findType) }, ObjectExpression: function(node) { return node.objType; }, + ClassExpression: function(node) { + return node.objType; + }, FunctionExpression: function(node) { - return node.body.scope.fnType; + return node.scope.fnType; + }, + ArrowFunctionExpression: function(node) { + return node.scope.fnType; }, SequenceExpression: function(node, scope) { return findType(node.expressions[node.expressions.length-1], scope); }, UnaryExpression: function(node) { return unopResultType(node.operator); }, UpdateExpression: function() { @@ -1314,133 +1870,222 @@ return lhs.isEmpty() ? findType(node.right, scope) : lhs; }, ConditionalExpression: function(node, scope) { var lhs = findType(node.consequent, scope); return lhs.isEmpty() ? findType(node.alternate, scope) : lhs; }, NewExpression: function(node, scope) { var f = findType(node.callee, scope).getFunctionType(); - var proto = f && f.getProp("prototype").getType(); + var proto = f && f.getProp("prototype").getObjType(); if (!proto) return ANull; return getInstance(proto, f); }, CallExpression: function(node, scope) { - var f = findType(node.callee, scope).getFunctionType(); - if (!f) return ANull; - if (f.computeRet) { - for (var i = 0, args = []; i < node.arguments.length; ++i) - args.push(findType(node.arguments[i], scope)); - var self = ANull; - if (node.callee.type == "MemberExpression") - self = findType(node.callee.object, scope); - return f.computeRet(self, args, node.arguments); - } else { - return f.retval; - } + return computeReturnType(node.callee, node.arguments, scope) }, MemberExpression: function(node, scope) { - var propN = propName(node, scope), obj = findType(node.object, scope).getType(); + var propN = propName(node), obj = findType(node.object, scope).getType(); if (obj) return obj.getProp(propN); if (propN == "<i>") return ANull; return findByPropertyName(propN); }, Identifier: function(node, scope) { return scope.hasProp(node.name) || ANull; }, ThisExpression: function(_node, scope) { - return scope.fnType ? scope.fnType.self : cx.topScope; + return getThis(scope) }, Literal: function(node) { - return literalType(node.value); + return literalType(node); + }, + Super: ret(function(node) { + return node.superType + }), + TemplateLiteral: function() { + return cx.str + }, + TaggedTemplateExpression: function(node, scope) { + return computeReturnType(node.tag, node.quasi.expressions, scope) + }, + YieldExpression: function(_node, scope) { + var fn = functionScope(scope).fnType + return fn ? fn.yieldval : ANull } }; function findType(node, scope) { - var found = typeFinder[node.type](node, scope); - return found; + var finder = typeFinder[node.type]; + return finder ? finder(node, scope) : ANull; } var searchVisitor = exports.searchVisitor = walk.make({ Function: function(node, _st, c) { - var scope = node.body.scope; - if (node.id) c(node.id, scope); - for (var i = 0; i < node.params.length; ++i) - c(node.params[i], scope); - c(node.body, scope, "ScopeBody"); + walk.base.Function(node, node.scope, c) + }, + Property: function(node, st, c) { + if (node.computed) c(node.key, st, "Expression"); + if (node.key != node.value) c(node.value, st, "Expression"); + }, + Statement: function(node, st, c) { + c(node, node.scope || st) }, - TryStatement: function(node, st, c) { - if (node.handler) - c(node.handler.param, st); - walk.base.TryStatement(node, st, c); + ImportSpecifier: function(node, st, c) { + c(node.local, st) }, - VariableDeclaration: function(node, st, c) { - for (var i = 0; i < node.declarations.length; ++i) { - var decl = node.declarations[i]; - c(decl.id, st); - if (decl.init) c(decl.init, st, "Expression"); - } + ImportDefaultSpecifier: function(node, st, c) { + c(node.local, st) + }, + ImportNamespaceSpecifier: function(node, st, c) { + c(node.local, st) } }); exports.fullVisitor = walk.make({ MemberExpression: function(node, st, c) { c(node.object, st, "Expression"); c(node.property, st, node.computed ? "Expression" : null); }, ObjectExpression: function(node, st, c) { for (var i = 0; i < node.properties.length; ++i) { c(node.properties[i].value, st, "Expression"); c(node.properties[i].key, st); } } }, searchVisitor); exports.findExpressionAt = function(ast, start, end, defaultScope, filter) { - var test = filter || function(_t, node) {return typeFinder.hasOwnProperty(node.type);}; + var test = filter || function(_t, node) { + if (node.type == "Identifier" && node.name == "✖") return false; + return typeFinder.hasOwnProperty(node.type); + }; return walk.findNodeAt(ast, start, end, test, searchVisitor, defaultScope || cx.topScope); }; exports.findExpressionAround = function(ast, start, end, defaultScope, filter) { var test = filter || function(_t, node) { if (start != null && node.start > start) return false; + if (node.type == "Identifier" && node.name == "✖") return false; return typeFinder.hasOwnProperty(node.type); }; return walk.findNodeAround(ast, end, test, searchVisitor, defaultScope || cx.topScope); }; exports.expressionType = function(found) { return findType(found.node, found.state); }; + // Finding the expected type of something, from context + + exports.parentNode = function(child, ast) { + var stack = []; + function c(node, st, override) { + if (node.start <= child.start && node.end >= child.end) { + var top = stack[stack.length - 1]; + if (node == child) throw {found: top}; + if (top != node) stack.push(node); + walk.base[override || node.type](node, st, c); + if (top != node) stack.pop(); + } + } + try { + c(ast, null); + } catch (e) { + if (e.found) return e.found; + throw e; + } + }; + + var findTypeFromContext = exports.findTypeFromContext = { + ArrayExpression: function(parent, _, get) { return get(parent, true).getProp("<i>"); }, + ObjectExpression: function(parent, node, get) { + for (var i = 0; i < parent.properties.length; ++i) { + var prop = node.properties[i]; + if (prop.value == node) + return get(parent, true).getProp(prop.key.name); + } + }, + UnaryExpression: function(parent) { return unopResultType(parent.operator); }, + UpdateExpression: function() { return cx.num; }, + BinaryExpression: function(parent) { return binopIsBoolean(parent.operator) ? cx.bool : cx.num; }, + AssignmentExpression: function(parent, _, get) { return get(parent.left); }, + LogicalExpression: function(parent, _, get) { return get(parent, true); }, + ConditionalExpression: function(parent, node, get) { + if (parent.consequent == node || parent.alternate == node) return get(parent, true); + }, + CallExpression: function(parent, node, get) { + for (var i = 0; i < parent.arguments.length; i++) { + var arg = parent.arguments[i]; + if (arg == node) { + var calleeType = get(parent.callee).getFunctionType(); + if (calleeType instanceof Fn) + return calleeType.args[i]; + break; + } + } + }, + ReturnStatement: function(_parent, node, get) { + var fnNode = walk.findNodeAround(node.sourceFile.ast, node.start, "Function"); + if (fnNode) { + var fnType = fnNode.node.type != "FunctionDeclaration" + ? get(fnNode.node, true).getFunctionType() + : fnNode.node.scope.fnType; + if (fnType) return fnType.retval.getType(); + } + }, + VariableDeclarator: function(parent, node, get) { + if (parent.init == node) return get(parent.id) + } + }; + findTypeFromContext.NewExpression = findTypeFromContext.CallExpression + + exports.typeFromContext = function(ast, found) { + var parent = exports.parentNode(found.node, ast); + var type = null; + if (findTypeFromContext.hasOwnProperty(parent.type)) { + var finder = findTypeFromContext[parent.type]; + type = finder && finder(parent, found.node, function(node, fromContext) { + var obj = {node: node, state: found.state}; + var tp = fromContext ? exports.typeFromContext(ast, obj) : exports.expressionType(obj); + return tp || ANull; + }); + } + return type || exports.expressionType(found); + }; + // Flag used to indicate that some wild guessing was used to produce // a type or set of completions. var guessing = false; exports.resetGuessing = function(val) { guessing = val; }; exports.didGuess = function() { return guessing; }; exports.forAllPropertiesOf = function(type, f) { type.gatherProperties(f, 0); }; var refFindWalker = walk.make({}, searchVisitor); exports.findRefs = function(ast, baseScope, name, refScope, f) { - refFindWalker.Identifier = function(node, scope) { + refFindWalker.Identifier = refFindWalker.VariablePattern = function(node, scope) { if (node.name != name) return; for (var s = scope; s; s = s.prev) { if (s == refScope) f(node, scope); if (name in s.props) return; } }; walk.recursive(ast, baseScope, null, refFindWalker); }; var simpleWalker = walk.make({ - Function: function(node, _st, c) { c(node.body, node.body.scope, "ScopeBody"); } + Function: function(node, _scope, c) { + c(node.body, node.scope, node.expression ? "Expression" : "Statement") + }, + Statement: function(node, scope, c) { + c(node, node.scope || scope) + } }); exports.findPropRefs = function(ast, scope, objType, propName, f) { walk.simple(ast, { MemberExpression: function(node, scope) { if (node.computed || node.property.name != propName) return; if (findType(node.object, scope).getType() == objType) f(node.property); }, @@ -1450,18 +2095,18 @@ if (node.properties[i].key.name == propName) f(node.properties[i].key); } }, simpleWalker, scope); }; // LOCAL-VARIABLE QUERIES var scopeAt = exports.scopeAt = function(ast, pos, defaultScope) { - var found = walk.findNodeAround(ast, pos, function(tp, node) { - return tp == "ScopeBody" && node.scope; + var found = walk.findNodeAround(ast, pos, function(_, node) { + return node.scope; }); if (found) return found.node.scope; else return defaultScope || cx.topScope; }; exports.forAllLocalsAt = function(ast, pos, defaultScope, f) { var scope = scopeAt(ast, pos, defaultScope); scope.gatherProperties(f, 0);
old mode 100644 new mode 100755 --- a/devtools/client/sourceeditor/tern/signal.js +++ b/devtools/client/sourceeditor/tern/signal.js @@ -1,26 +1,51 @@ -(function(mod) { +(function(root, mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS return mod(exports); if (typeof define == "function" && define.amd) // AMD return define(["exports"], mod); - mod((self.tern || (self.tern = {})).signal = {}); // Plain browser env -})(function(exports) { + mod((root.tern || (root.tern = {})).signal = {}); // Plain browser env +})(this, function(exports) { + function on(type, f) { var handlers = this._handlers || (this._handlers = Object.create(null)); (handlers[type] || (handlers[type] = [])).push(f); } + function off(type, f) { var arr = this._handlers && this._handlers[type]; if (arr) for (var i = 0; i < arr.length; ++i) if (arr[i] == f) { arr.splice(i, 1); break; } } + + var noHandlers = [] + function getHandlers(emitter, type) { + var arr = emitter._handlers && emitter._handlers[type]; + return arr && arr.length ? arr.slice() : noHandlers + } + function signal(type, a1, a2, a3, a4) { - var arr = this._handlers && this._handlers[type]; - if (arr) for (var i = 0; i < arr.length; ++i) arr[i].call(this, a1, a2, a3, a4); + var arr = getHandlers(this, type) + for (var i = 0; i < arr.length; ++i) arr[i].call(this, a1, a2, a3, a4) + } + + function signalReturnFirst(type, a1, a2, a3, a4) { + var arr = getHandlers(this, type) + for (var i = 0; i < arr.length; ++i) { + var result = arr[i].call(this, a1, a2, a3, a4) + if (result) return result + } + } + + function hasHandler(type) { + var arr = this._handlers && this._handlers[type] + return arr && arr.length > 0 && arr } exports.mixin = function(obj) { - obj.on = on; obj.off = off; obj.signal = signal; + obj.on = on; obj.off = off; + obj.signal = signal; + obj.signalReturnFirst = signalReturnFirst; + obj.hasHandler = hasHandler; return obj; }; });
old mode 100644 new mode 100755 --- a/devtools/client/sourceeditor/tern/tern.js +++ b/devtools/client/sourceeditor/tern/tern.js @@ -1,35 +1,40 @@ // The Tern server object // A server is a stateful object that manages the analysis for a // project, and defines an interface for querying the code in the // project. -(function(mod) { +(function(root, mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS return mod(exports, require("./infer"), require("./signal"), - require("acorn/acorn"), require("acorn/util/walk")); + require("acorn/acorn"), require("acorn/walk")); if (typeof define == "function" && define.amd) // AMD - return define(["exports", "./infer", "./signal", "acorn/acorn", "acorn/util/walk"], mod); - mod(self.tern || (self.tern = {}), tern, tern.signal, acorn, acorn.walk); // Plain browser env -})(function(exports, infer, signal, acorn, walk) { + return define(["exports", "./infer", "./signal", "acorn/acorn", "acorn/walk"], mod); + mod(root.tern || (root.tern = {}), tern, tern.signal, acorn, acorn.walk); // Plain browser env +})(this, function(exports, infer, signal, acorn, walk) { "use strict"; var plugins = Object.create(null); exports.registerPlugin = function(name, init) { plugins[name] = init; }; var defaultOptions = exports.defaultOptions = { debug: false, async: false, getFile: function(_f, c) { if (this.async) c(null, null); }, + normalizeFilename: function(name) { return name }, defs: [], plugins: {}, fetchTimeout: 1000, - dependencyBudget: 20000 + dependencyBudget: 20000, + reuseInstances: true, + stripCRs: false, + ecmaVersion: 6, + projectDir: "/" }; var queryTypes = { completions: { takesFile: true, run: findCompletions }, properties: { @@ -66,73 +71,89 @@ function File(name, parent) { this.name = name; this.parent = parent; this.scope = this.text = this.ast = this.lineOffsets = null; } File.prototype.asLineChar = function(pos) { return asLineChar(this, pos); }; + function parseFile(srv, file) { + var options = { + directSourceFile: file, + allowReturnOutsideFunction: true, + allowImportExportEverywhere: true, + ecmaVersion: srv.options.ecmaVersion + } + var text = srv.signalReturnFirst("preParse", file.text, options) || file.text + var ast = infer.parse(text, options) + srv.signal("postParse", ast, text) + return ast + } + function updateText(file, text, srv) { - file.text = text; - file.ast = infer.parse(text, srv.passes, {directSourceFile: file, allowReturnOutsideFunction: true}); + file.text = srv.options.stripCRs ? text.replace(/\r\n/g, "\n") : text; + infer.withContext(srv.cx, function() { + file.ast = parseFile(srv, file) + }); file.lineOffsets = null; } var Server = exports.Server = function(options) { this.cx = null; this.options = options || {}; for (var o in defaultOptions) if (!options.hasOwnProperty(o)) options[o] = defaultOptions[o]; + this.projectDir = options.projectDir.replace(/\\/g, "/") + if (!/\/$/.test(this.projectDir)) this.projectDir += "/" + this.handlers = Object.create(null); this.files = []; this.fileMap = Object.create(null); + this.needsPurge = []; this.budgets = Object.create(null); this.uses = 0; this.pending = 0; this.asyncError = null; - this.passes = Object.create(null); + this.mod = {} - this.defs = options.defs.slice(0); - for (var plugin in options.plugins) if (options.plugins.hasOwnProperty(plugin) && plugin in plugins) { - var init = plugins[plugin](this, options.plugins[plugin]); - if (init && init.defs) { - if (init.loadFirst) this.defs.unshift(init.defs); - else this.defs.push(init.defs); - } - if (init && init.passes) for (var type in init.passes) if (init.passes.hasOwnProperty(type)) - (this.passes[type] || (this.passes[type] = [])).push(init.passes[type]); - } + this.defs = options.defs.slice(0) + this.plugins = Object.create(null) + for (var plugin in options.plugins) if (options.plugins.hasOwnProperty(plugin)) + this.loadPlugin(plugin, options.plugins[plugin]) this.reset(); }; Server.prototype = signal.mixin({ addFile: function(name, /*optional*/ text, parent) { // Don't crash when sloppy plugins pass non-existent parent ids - if (parent && !parent in this.fileMap) parent = null; + if (parent && !(parent in this.fileMap)) parent = null; + if (!(name in this.fileMap)) + name = this.normalizeFilename(name) ensureFile(this, name, parent, text); }, delFile: function(name) { - for (var i = 0, f; i < this.files.length; ++i) if ((f = this.files[i]).name == name) { - clearFile(this, f, null, true); - this.files.splice(i--, 1); + var file = this.findFile(name); + if (file) { + this.needsPurge.push(file.name); + this.files.splice(this.files.indexOf(file), 1); delete this.fileMap[name]; - return; } }, reset: function() { this.signal("reset"); this.cx = new infer.Context(this.defs, this); this.uses = 0; this.budgets = Object.create(null); for (var i = 0; i < this.files.length; ++i) { var file = this.files[i]; file.scope = null; } + this.signal("postReset"); }, request: function(doc, c) { var inv = invalidDoc(doc); if (inv) return c(inv); var self = this; doRequest(this, doc, function(err, data) { @@ -156,33 +177,62 @@ }); }, startAsyncAction: function() { ++this.pending; }, finishAsyncAction: function(err) { if (err) this.asyncError = err; - if (--this.pending == 0) this.signal("everythingFetched"); + if (--this.pending === 0) this.signal("everythingFetched"); + }, + + addDefs: function(defs, toFront) { + if (toFront) this.defs.unshift(defs) + else this.defs.push(defs) + + if (this.cx) this.reset() + }, + + loadPlugin: function(name, options) { + if (arguments.length == 1) options = this.options.plugins[name] || true + if (name in this.plugins || !(name in plugins) || !options) return + this.plugins[name] = true + var init = plugins[name](this, options) + + // This is for backwards-compatibilty. Don't rely on it -- use addDef and on directly + if (!init) return + if (init.defs) this.addDefs(init.defs, init.loadFirst) + if (init.passes) for (var type in init.passes) if (init.passes.hasOwnProperty(type)) + this.on(type, init.passes[type]) + }, + + normalizeFilename: function(name) { + var norm = this.options.normalizeFilename(name).replace(/\\/g, "/") + if (norm.indexOf(this.projectDir) == 0) norm = norm.slice(this.projectDir.length) + return norm } }); function doRequest(srv, doc, c) { if (doc.query && !queryTypes.hasOwnProperty(doc.query.type)) return c("No query type '" + doc.query.type + "' defined"); var query = doc.query; // Respond as soon as possible when this just uploads files if (!query) c(null, {}); var files = doc.files || []; if (files.length) ++srv.uses; for (var i = 0; i < files.length; ++i) { var file = files[i]; - ensureFile(srv, file.name, null, file.type == "full" ? file.text : null); + if (file.type == "delete") + srv.delFile(file.name); + else + ensureFile(srv, file.name, null, file.type == "full" ? file.text : null); } var timeBudget = typeof doc.timeout == "number" ? [doc.timeout] : null; if (!query) { analyzeAll(srv, timeBudget, function(){}); return; } @@ -203,37 +253,42 @@ try { result = queryType.run(srv, query, file); } catch (e) { if (srv.options.debug && e.name != "TernError") console.error(e.stack); return c(e); } c(null, result); } + infer.resetGuessing() infer.withContext(srv.cx, timeBudget ? function() { infer.withTimeout(timeBudget[0], run); } : run); }); } function analyzeFile(srv, file) { infer.withContext(srv.cx, function() { file.scope = srv.cx.topScope; srv.signal("beforeLoad", file); - infer.markVariablesDefinedBy(file.scope, file.name); - infer.analyze(file.ast, file.name, file.scope, srv.passes); - infer.purgeMarkedVariables(file.scope); + infer.analyze(file.ast, file.name, file.scope); srv.signal("afterLoad", file); }); return file; } function ensureFile(srv, name, parent, text) { var known = srv.findFile(name); if (known) { - if (text != null) clearFile(srv, known, text); - if (parentDepth(known.parent) > parentDepth(parent)) { + if (text != null) { + if (known.scope) { + srv.needsPurge.push(name); + known.scope = null; + } + updateText(known, text, srv); + } + if (parentDepth(srv, known.parent) > parentDepth(srv, parent)) { known.parent = parent; if (known.excluded) known.excluded = null; } return; } var file = new File(name, parent); srv.files.push(file); @@ -246,50 +301,33 @@ updateText(file, text || "", srv); srv.finishAsyncAction(err); }); } else { updateText(file, srv.options.getFile(name) || "", srv); } } - function clearFile(srv, file, newText, purgeVars) { - if (file.scope) { - infer.withContext(srv.cx, function() { - // FIXME try to batch purges into a single pass (each call needs - // to traverse the whole graph) - infer.purgeTypes(file.name); - if (purgeVars) { - infer.markVariablesDefinedBy(file.scope, file.name); - infer.purgeMarkedVariables(file.scope); - } - }); - file.scope = null; - } - if (newText != null) updateText(file, newText, srv); - } - function fetchAll(srv, c) { var done = true, returned = false; - for (var i = 0; i < srv.files.length; ++i) { - var file = srv.files[i]; - if (file.text != null) continue; + srv.files.forEach(function(file) { + if (file.text != null) return; if (srv.options.async) { done = false; srv.options.getFile(file.name, function(err, text) { if (err && !returned) { returned = true; return c(err); } updateText(file, text || "", srv); fetchAll(srv, c); }); } else { try { updateText(file, srv.options.getFile(file.name) || "", srv); } catch (e) { return c(e); } } - } + }); if (done) c(); } function waitOnFetch(srv, timeBudget, c) { var done = function() { srv.off("everythingFetched", done); clearTimeout(timeout); analyzeAll(srv, timeBudget, c); @@ -299,27 +337,34 @@ } function analyzeAll(srv, timeBudget, c) { if (srv.pending) return waitOnFetch(srv, timeBudget, c); var e = srv.fetchError; if (e) { srv.fetchError = null; return c(e); } + if (srv.needsPurge.length > 0) infer.withContext(srv.cx, function() { + infer.purge(srv.needsPurge); + srv.needsPurge.length = 0; + }); + var done = true; // The second inner loop might add new files. The outer loop keeps // repeating both inner loops until all files have been looked at. for (var i = 0; i < srv.files.length;) { var toAnalyze = []; for (; i < srv.files.length; ++i) { var file = srv.files[i]; if (file.text == null) done = false; else if (file.scope == null && !file.excluded) toAnalyze.push(file); } - toAnalyze.sort(function(a, b) { return parentDepth(a.parent) - parentDepth(b.parent); }); + toAnalyze.sort(function(a, b) { + return parentDepth(srv, a.parent) - parentDepth(srv, b.parent); + }); for (var j = 0; j < toAnalyze.length; j++) { var file = toAnalyze[j]; if (file.parent && !chargeOnBudget(srv, file)) { file.excluded = true; } else if (timeBudget) { var startTime = +new Date; infer.withTimeout(timeBudget[0], function() { analyzeFile(srv, file); }); timeBudget[0] -= +new Date - startTime; @@ -361,52 +406,51 @@ return err; } function resolveFile(srv, localFiles, name) { var isRef = name.match(/^#(\d+)$/); if (!isRef) return srv.findFile(name); var file = localFiles[isRef[1]]; - if (!file) throw ternError("Reference to unknown file " + name); + if (!file || file.type == "delete") throw ternError("Reference to unknown file " + name); if (file.type == "full") return srv.findFile(file.name); // This is a partial file var realFile = file.backing = srv.findFile(file.name); var offset = file.offset; if (file.offsetLines) offset = {line: file.offsetLines, ch: 0}; file.offset = offset = resolvePos(realFile, file.offsetLines == null ? file.offset : {line: file.offsetLines, ch: 0}, true); var line = firstLine(file.text); var foundPos = findMatchingPosition(line, realFile.text, offset); var pos = foundPos == null ? Math.max(0, realFile.text.lastIndexOf("\n", offset)) : foundPos; + var inObject, atFunction; infer.withContext(srv.cx, function() { - infer.purgeTypes(file.name, pos, pos + file.text.length); + infer.purge(file.name, pos, pos + file.text.length); var text = file.text, m; if (m = text.match(/(?:"([^"]*)"|([\w$]+))\s*:\s*function\b/)) { var objNode = walk.findNodeAround(file.backing.ast, pos, "ObjectExpression"); if (objNode && objNode.node.objType) - var inObject = {type: objNode.node.objType, prop: m[2] || m[1]}; + inObject = {type: objNode.node.objType, prop: m[2] || m[1]}; } if (foundPos && (m = line.match(/^(.*?)\bfunction\b/))) { var cut = m[1].length, white = ""; for (var i = 0; i < cut; ++i) white += " "; - text = white + text.slice(cut); - var atFunction = true; + file.text = white + text.slice(cut); + atFunction = true; } var scopeStart = infer.scopeAt(realFile.ast, pos, realFile.scope); var scopeEnd = infer.scopeAt(realFile.ast, pos + text.length, realFile.scope); var scope = file.scope = scopeDepth(scopeStart) < scopeDepth(scopeEnd) ? scopeEnd : scopeStart; - infer.markVariablesDefinedBy(scopeStart, file.name, pos, pos + file.text.length); - file.ast = infer.parse(file.text, srv.passes, {directSourceFile: file, allowReturnOutsideFunction: true}); - infer.analyze(file.ast, file.name, scope, srv.passes); - infer.purgeMarkedVariables(scopeStart); + file.ast = parseFile(srv, file) + infer.analyze(file.ast, file.name, scope); // This is a kludge to tie together the function types (if any) // outside and inside of the fragment, so that arguments and // return values have some information known about them. tieTogether: if (inObject || atFunction) { var newInner = infer.scopeAt(file.ast, line.length, scopeStart); if (!newInner.fnType) break tieTogether; if (inObject) { @@ -478,18 +522,19 @@ if (doc.query.start && !isPosition(doc.query.start)) return ".query.start must be a position"; if (doc.query.end && !isPosition(doc.query.end)) return ".query.end must be a position"; } if (doc.files) { if (!Array.isArray(doc.files)) return "Files property must be an array"; for (var i = 0; i < doc.files.length; ++i) { var file = doc.files[i]; if (typeof file != "object") return ".files[n] must be objects"; + else if (typeof file.name != "string") return ".files[n].name must be a string"; + else if (file.type == "delete") continue; else if (typeof file.text != "string") return ".files[n].text must be a string"; - else if (typeof file.name != "string") return ".files[n].name must be a string"; else if (file.type == "part") { if (!isPosition(file.offset) && typeof file.offsetLines != "number") return ".files[n].offset must be a position"; } else if (file.type != "full") return ".files[n].type must be \"full\" or \"part\""; } } } @@ -499,38 +544,38 @@ var text = file.text, offsets = file.lineOffsets || (file.lineOffsets = [0]); var pos = 0, curLine = 0; var storePos = Math.min(Math.floor(line / offsetSkipLines), offsets.length - 1); var pos = offsets[storePos], curLine = storePos * offsetSkipLines; while (curLine < line) { ++curLine; pos = text.indexOf("\n", pos) + 1; - if (pos == 0) return null; - if (curLine % offsetSkipLines == 0) offsets.push(pos); + if (pos === 0) return null; + if (curLine % offsetSkipLines === 0) offsets.push(pos); } return pos; } - function resolvePos(file, pos, tolerant) { + var resolvePos = exports.resolvePos = function(file, pos, tolerant) { if (typeof pos != "number") { var lineStart = findLineStart(file, pos.line); if (lineStart == null) { if (tolerant) pos = file.text.length; else throw ternError("File doesn't contain a line " + pos.line); } else { pos = lineStart + pos.ch; } } if (pos > file.text.length) { if (tolerant) pos = file.text.length; else throw ternError("Position " + pos + " is outside of file."); } return pos; - } + }; function asLineChar(file, pos) { if (!file) return {line: 0, ch: 0}; var offsets = file.lineOffsets || (file.lineOffsets = [0]); var text = file.text, line, lineStart; for (var i = offsets.length - 1; i >= 0; --i) if (offsets[i] <= pos) { line = i * offsetSkipLines; lineStart = offsets[i]; @@ -539,26 +584,26 @@ var eol = text.indexOf("\n", lineStart); if (eol >= pos || eol < 0) break; lineStart = eol + 1; ++line; } return {line: line, ch: pos - lineStart}; } - function outputPos(query, file, pos) { + var outputPos = exports.outputPos = function(query, file, pos) { if (query.lineCharPositions) { var out = asLineChar(file, pos); if (file.type == "part") out.line += file.offsetLines != null ? file.offsetLines : asLineChar(file.backing, file.offset).line; return out; } else { return pos + (file.type == "part" ? file.offset : 0); } - } + }; // Delete empty fields from result objects function clean(obj) { for (var prop in obj) if (obj[prop] == null) delete obj[prop]; return obj; } function maybeSet(obj, prop, val) { if (val != null) obj[prop] = val; @@ -573,165 +618,284 @@ else return aUp ? 1 : -1; } function isStringAround(node, start, end) { return node.type == "Literal" && typeof node.value == "string" && node.start == start - 1 && node.end <= end + 1; } + function pointInProp(objNode, point) { + for (var i = 0; i < objNode.properties.length; i++) { + var curProp = objNode.properties[i]; + if (curProp.key.start <= point && curProp.key.end >= point) + return curProp; + } + } + var jsKeywords = ("break do instanceof typeof case else new var " + "catch finally return void continue for switch while debugger " + "function this with default if throw delete in try").split(" "); + var addCompletion = exports.addCompletion = function(query, completions, name, aval, depth) { + var typeInfo = query.types || query.docs || query.urls || query.origins; + var wrapAsObjs = typeInfo || query.depths; + + for (var i = 0; i < completions.length; ++i) { + var c = completions[i]; + if ((wrapAsObjs ? c.name : c) == name) return; + } + var rec = wrapAsObjs ? {name: name} : name; + completions.push(rec); + + if (aval && typeInfo) { + infer.resetGuessing(); + var type = aval.getType(); + rec.guess = infer.didGuess(); + if (query.types) + rec.type = infer.toString(aval); + if (query.docs) + maybeSet(rec, "doc", parseDoc(query, aval.doc || type && type.doc)); + if (query.urls) + maybeSet(rec, "url", aval.url || type && type.url); + if (query.origins) + maybeSet(rec, "origin", aval.origin || type && type.origin); + } + if (query.depths) rec.depth = depth || 0; + return rec; + }; + function findCompletions(srv, query, file) { if (query.end == null) throw ternError("missing .query.end field"); + var fromPlugin = srv.signalReturnFirst("completion", file, query) + if (fromPlugin) return fromPlugin + var wordStart = resolvePos(file, query.end), wordEnd = wordStart, text = file.text; while (wordStart && acorn.isIdentifierChar(text.charCodeAt(wordStart - 1))) --wordStart; if (query.expandWordForward !== false) while (wordEnd < text.length && acorn.isIdentifierChar(text.charCodeAt(wordEnd))) ++wordEnd; - var word = text.slice(wordStart, wordEnd), completions = []; + var word = text.slice(wordStart, wordEnd), completions = [], ignoreObj; if (query.caseInsensitive) word = word.toLowerCase(); - var wrapAsObjs = query.types || query.depths || query.docs || query.urls || query.origins; - function gather(prop, obj, depth) { + function gather(prop, obj, depth, addInfo) { // 'hasOwnProperty' and such are usually just noise, leave them // out when no prefix is provided. - if (query.omitObjectPrototype !== false && obj == srv.cx.protos.Object && !word) return; + if ((objLit || query.omitObjectPrototype !== false) && obj == srv.cx.protos.Object && !word) return; if (query.filter !== false && word && - (query.caseInsensitive ? prop.toLowerCase() : prop).indexOf(word) != 0) return; - for (var i = 0; i < completions.length; ++i) { - var c = completions[i]; - if ((wrapAsObjs ? c.name : c) == prop) return; - } - var rec = wrapAsObjs ? {name: prop} : prop; - completions.push(rec); - - if (query.types || query.docs || query.urls || query.origins) { - var val = obj ? obj.props[prop] : infer.ANull; - infer.resetGuessing(); - var type = val.getType(); - rec.guess = infer.didGuess(); - if (query.types) - rec.type = infer.toString(type); - if (query.docs) - maybeSet(rec, "doc", val.doc || type && type.doc); - if (query.urls) - maybeSet(rec, "url", val.url || type && type.url); - if (query.origins) - maybeSet(rec, "origin", val.origin || type && type.origin); - } - if (query.depths) rec.depth = depth; + (query.caseInsensitive ? prop.toLowerCase() : prop).indexOf(word) !== 0) return; + if (ignoreObj && ignoreObj.props[prop]) return; + var result = addCompletion(query, completions, prop, obj && obj.props[prop], depth); + if (addInfo && result && typeof result != "string") addInfo(result); } - var memberExpr = infer.findExpressionAround(file.ast, null, wordStart, file.scope, "MemberExpression"); - var hookname; - if (memberExpr && - (memberExpr.node.computed ? isStringAround(memberExpr.node.property, wordStart, wordEnd) - : memberExpr.node.object.end < wordStart)) { - var prop = memberExpr.node.property; + var hookname, prop, objType, isKey; + + var exprAt = infer.findExpressionAround(file.ast, null, wordStart, file.scope); + var memberExpr, objLit; + // Decide whether this is an object property, either in a member + // expression or an object literal. + if (exprAt) { + var exprNode = exprAt.node; + if (exprNode.type == "MemberExpression" && exprNode.object.end < wordStart) { + memberExpr = exprAt; + } else if (isStringAround(exprNode, wordStart, wordEnd)) { + var parent = infer.parentNode(exprNode, file.ast); + if (parent.type == "MemberExpression" && parent.property == exprNode) + memberExpr = {node: parent, state: exprAt.state}; + } else if (exprNode.type == "ObjectExpression") { + var objProp = pointInProp(exprNode, wordEnd); + if (objProp) { + objLit = exprAt; + prop = isKey = objProp.key.name; + } else if (!word && !/:\s*$/.test(file.text.slice(0, wordStart))) { + objLit = exprAt; + prop = isKey = true; + } + } + } + + if (objLit) { + // Since we can't use the type of the literal itself to complete + // its properties (it doesn't contain the information we need), + // we have to try asking the surrounding expression for type info. + objType = infer.typeFromContext(file.ast, objLit); + ignoreObj = objLit.node.objType; + } else if (memberExpr) { + prop = memberExpr.node.property; prop = prop.type == "Literal" ? prop.value.slice(1) : prop.name; + memberExpr.node = memberExpr.node.object; + objType = infer.expressionType(memberExpr); + } else if (text.charAt(wordStart - 1) == ".") { + var pathStart = wordStart - 1; + while (pathStart && (text.charAt(pathStart - 1) == "." || acorn.isIdentifierChar(text.charCodeAt(pathStart - 1)))) pathStart--; + var path = text.slice(pathStart, wordStart - 1); + if (path) { + objType = infer.def.parsePath(path, file.scope).getObjType(); + prop = word; + } + } + + if (prop != null) { srv.cx.completingProperty = prop; - memberExpr.node = memberExpr.node.object; - var tp = infer.expressionType(memberExpr); - if (tp) infer.forAllPropertiesOf(tp, gather); + if (objType) infer.forAllPropertiesOf(objType, gather); - if (!completions.length && query.guess !== false && tp && tp.guessProperties) { - tp.guessProperties(function(p, o, d) {if (p != prop && p != "✖") gather(p, o, d);}); - } + if (!completions.length && query.guess !== false && objType && objType.guessProperties) + objType.guessProperties(function(p, o, d) {if (p != prop && p != "✖") gather(p, o, d);}); if (!completions.length && word.length >= 2 && query.guess !== false) for (var prop in srv.cx.props) gather(prop, srv.cx.props[prop][0], 0); hookname = "memberCompletion"; } else { infer.forAllLocalsAt(file.ast, wordStart, file.scope, gather); - if (query.includeKeywords) jsKeywords.forEach(function(kw) { gather(kw, null, 0); }); - hookname = "completion"; + if (query.includeKeywords) jsKeywords.forEach(function(kw) { + gather(kw, null, 0, function(rec) { rec.isKeyword = true; }); + }); + hookname = "variableCompletion"; } - if (srv.passes[hookname]) - srv.passes[hookname].forEach(function(hook) {hook(file, wordStart, wordEnd, gather);}); + srv.signal(hookname, file, wordStart, wordEnd, gather) if (query.sort !== false) completions.sort(compareCompletions); srv.cx.completingProperty = null; return {start: outputPos(query, file, wordStart), end: outputPos(query, file, wordEnd), + isProperty: !!prop, + isObjectKey: !!isKey, completions: completions}; } function findProperties(srv, query) { var prefix = query.prefix, found = []; for (var prop in srv.cx.props) - if (prop != "<i>" && (!prefix || prop.indexOf(prefix) == 0)) found.push(prop); + if (prop != "<i>" && (!prefix || prop.indexOf(prefix) === 0)) found.push(prop); if (query.sort !== false) found.sort(compareCompletions); return {completions: found}; } var findExpr = exports.findQueryExpr = function(file, query, wide) { if (query.end == null) throw ternError("missing .query.end field"); if (query.variable) { var scope = infer.scopeAt(file.ast, resolvePos(file, query.end), file.scope); return {node: {type: "Identifier", name: query.variable, start: query.end, end: query.end + 1}, state: scope}; } else { var start = query.start && resolvePos(file, query.start), end = resolvePos(file, query.end); var expr = infer.findExpressionAt(file.ast, start, end, file.scope); if (expr) return expr; expr = infer.findExpressionAround(file.ast, start, end, file.scope); - if (expr && (wide || (start == null ? end : start) - expr.node.start < 20 || expr.node.end - end < 20)) + if (expr && (expr.node.type == "ObjectExpression" || wide || + (start == null ? end : start) - expr.node.start < 20 || expr.node.end - end < 20)) return expr; - throw ternError("No expression at the given position."); + return null; } }; - function findTypeAt(_srv, query, file) { - var expr = findExpr(file, query); - infer.resetGuessing(); - var type = infer.expressionType(expr); + function findExprOrThrow(file, query, wide) { + var expr = findExpr(file, query, wide); + if (expr) return expr; + throw ternError("No expression at the given position."); + } + + function ensureObj(tp) { + if (!tp || !(tp = tp.getType()) || !(tp instanceof infer.Obj)) return null; + return tp; + } + + function findExprType(srv, query, file, expr) { + var type; + if (expr) { + infer.resetGuessing(); + type = infer.expressionType(expr); + } + var typeHandlers = srv.hasHandler("typeAt") + if (typeHandlers) { + var pos = resolvePos(file, query.end) + for (var i = 0; i < typeHandlers.length; i++) + type = typeHandlers[i](file, pos, expr, type) + } + if (!type) throw ternError("No type found at the given position."); + + var objProp; + if (expr.node.type == "ObjectExpression" && query.end != null && + (objProp = pointInProp(expr.node, resolvePos(file, query.end)))) { + var name = objProp.key.name; + var fromCx = ensureObj(infer.typeFromContext(file.ast, expr)); + if (fromCx && fromCx.hasProp(name)) { + type = fromCx.hasProp(name); + } else { + var fromLocal = ensureObj(type); + if (fromLocal && fromLocal.hasProp(name)) + type = fromLocal.hasProp(name); + } + } + return type; + }; + + function findTypeAt(srv, query, file) { + var expr = findExpr(file, query), exprName; + var type = findExprType(srv, query, file, expr), exprType = type; if (query.preferFunction) type = type.getFunctionType() || type.getType(); else type = type.getType(); - if (expr.node.type == "Identifier") - var exprName = expr.node.name; - else if (expr.node.type == "MemberExpression" && !expr.node.computed) - var exprName = expr.node.property.name; + if (expr) { + if (expr.node.type == "Identifier") + exprName = expr.node.name; + else if (expr.node.type == "MemberExpression" && !expr.node.computed) + exprName = expr.node.property.name; + } if (query.depth != null && typeof query.depth != "number") throw ternError(".query.depth must be a number"); var result = {guess: infer.didGuess(), - type: infer.toString(type, query.depth), + type: infer.toString(exprType, query.depth), name: type && type.name, - exprName: exprName}; - if (type) storeTypeDocs(type, result); + exprName: exprName, + doc: exprType.doc, + url: exprType.url}; + if (type) storeTypeDocs(query, type, result); return clean(result); } - function findDocs(_srv, query, file) { + function parseDoc(query, doc) { + if (!doc) return null; + if (query.docFormat == "full") return doc; + var parabreak = /.\n[\s@\n]/.exec(doc); + if (parabreak) doc = doc.slice(0, parabreak.index + 1); + doc = doc.replace(/\n\s*/g, " "); + if (doc.length < 100) return doc; + var sentenceEnd = /[\.!?] [A-Z]/g; + sentenceEnd.lastIndex = 80; + var found = sentenceEnd.exec(doc); + if (found) doc = doc.slice(0, found.index + 1); + return doc; + } + + function findDocs(srv, query, file) { var expr = findExpr(file, query); - var type = infer.expressionType(expr); - var result = {url: type.url, doc: type.doc}; + var type = findExprType(srv, query, file, expr); + var result = {url: type.url, doc: parseDoc(query, type.doc), type: infer.toString(type)}; var inner = type.getType(); - if (inner) storeTypeDocs(inner, result); + if (inner) storeTypeDocs(query, inner, result); return clean(result); } - function storeTypeDocs(type, out) { + function storeTypeDocs(query, type, out) { if (!out.url) out.url = type.url; - if (!out.doc) out.doc = type.doc; + if (!out.doc) out.doc = parseDoc(query, type.doc); if (!out.origin) out.origin = type.origin; var ctor, boring = infer.cx().protos; if (!out.url && !out.doc && type.proto && (ctor = type.proto.hasCtor) && type.proto != boring.Object && type.proto != boring.Function && type.proto != boring.Array) { out.url = ctor.url; - out.doc = ctor.doc; + out.doc = parseDoc(query, ctor.doc); } } var getSpan = exports.getSpan = function(obj) { if (!obj.origin) return; if (obj.originNode) { var node = obj.originNode; if (/^Function/.test(node.type) && node.id) node = node.id; @@ -750,26 +914,25 @@ var file = srv.findFile(span.origin); target.start = outputPos(query, file, span.node.start); target.end = outputPos(query, file, span.node.end); } }; function findDef(srv, query, file) { var expr = findExpr(file, query); - infer.resetGuessing(); - var type = infer.expressionType(expr); + var type = findExprType(srv, query, file, expr); if (infer.didGuess()) return {}; var span = getSpan(type); - var result = {url: type.url, doc: type.doc, origin: type.origin}; + var result = {url: type.url, doc: parseDoc(query, type.doc), origin: type.origin}; if (type.types) for (var i = type.types.length - 1; i >= 0; --i) { var tp = type.types[i]; - storeTypeDocs(tp, result); + storeTypeDocs(query, tp, result); if (!span) span = getSpan(tp); } if (span && span.node) { // refers to a loaded file var spanFile = span.node.sourceFile || srv.findFile(span.origin); var start = outputPos(query, spanFile, span.node.start), end = outputPos(query, spanFile, span.node.end); result.start = start; result.end = end; result.file = span.origin; @@ -782,17 +945,17 @@ } return clean(result); } function findRefsToVariable(srv, query, file, expr, checkShadowing) { var name = expr.node.name; for (var scope = expr.state; scope && !(name in scope.props); scope = scope.prev) {} - if (!scope) throw ternError("Could not find a definition for " + name + " " + !!srv.cx.topScope.props.x); + if (!scope) throw ternError("Could not find a definition for " + name); var type, refs = []; function storeRef(file) { return function(node, scopeHere) { if (checkShadowing) for (var s = scopeHere; s != scope; s = s.prev) { var exists = s.hasProp(checkShadowing); if (exists) throw ternError("Renaming `" + name + "` to `" + checkShadowing + "` would make a variable at line " + @@ -823,17 +986,17 @@ infer.findRefs(cur.ast, cur.scope, name, scope, storeRef(cur)); } } return {refs: refs, type: type, name: name}; } function findRefsToProperty(srv, query, expr, prop) { - var objType = infer.expressionType(expr).getType(); + var objType = infer.expressionType(expr).getObjType(); if (!objType) throw ternError("Couldn't determine type of base object."); var refs = []; function storeRef(file) { return function(node) { refs.push({file: file.name, start: outputPos(query, file, node.start), end: outputPos(query, file, node.end)}); @@ -843,17 +1006,17 @@ var cur = srv.files[i]; infer.findPropRefs(cur.ast, cur.scope, objType, prop.name, storeRef(cur)); } return {refs: refs, name: prop.name}; } function findRefs(srv, query, file) { - var expr = findExpr(file, query, true); + var expr = findExprOrThrow(file, query, true); if (expr && expr.node.type == "Identifier") { return findRefsToVariable(srv, query, file, expr); } else if (expr && expr.node.type == "MemberExpression" && !expr.node.computed) { var p = expr.node.property; expr.node = expr.node.object; return findRefsToProperty(srv, query, expr, p); } else if (expr && expr.node.type == "ObjectExpression") { var pos = resolvePos(file, query.end); @@ -863,17 +1026,17 @@ return findRefsToProperty(srv, query, expr, k); } } throw ternError("Not at a variable or property name."); } function buildRename(srv, query, file) { if (typeof query.newName != "string") throw ternError(".query.newName should be a string"); - var expr = findExpr(file, query); + var expr = findExprOrThrow(file, query); if (!expr || expr.node.type != "Identifier") throw ternError("Not at a variable."); var data = findRefsToVariable(srv, query, file, expr, query.newName), refs = data.refs; delete data.refs; data.files = srv.files.map(function(f){return f.name;}); var changes = data.changes = []; for (var i = 0; i < refs.length; ++i) { @@ -884,10 +1047,10 @@ return data; } function listFiles(srv) { return {files: srv.files.map(function(f){return f.name;})}; } - exports.version = "0.6.2"; + exports.version = "0.16.0"; });
--- a/devtools/client/styleeditor/StyleSheetEditor.jsm +++ b/devtools/client/styleeditor/StyleSheetEditor.jsm @@ -356,16 +356,17 @@ StyleSheetEditor.prototype = { } else if (this.sourceEditor) { this._getSourceTextAndPrettify().then((newText) => { this._justSetText = true; let firstLine = this.sourceEditor.getFirstVisibleLine(); let pos = this.sourceEditor.getCursor(); this.sourceEditor.setText(newText); this.sourceEditor.setFirstVisibleLine(firstLine); this.sourceEditor.setCursor(pos); + this.emit("style-applied"); }); } }, /** * Handles changes to the list of @media rules in the stylesheet. * Emits 'media-rules-changed' if the list has changed. *
--- a/devtools/client/styleeditor/test/browser.ini +++ b/devtools/client/styleeditor/test/browser.ini @@ -51,17 +51,16 @@ support-files = sync.html [browser_styleeditor_autocomplete.js] [browser_styleeditor_autocomplete-disabled.js] [browser_styleeditor_bug_740541_iframes.js] [browser_styleeditor_bug_851132_middle_click.js] [browser_styleeditor_bug_870339.js] [browser_styleeditor_cmd_edit.js] -skip-if = e10s # Bug 1055333 - style editor tests disabled with e10s [browser_styleeditor_enabled.js] [browser_styleeditor_fetch-from-cache.js] [browser_styleeditor_filesave.js] [browser_styleeditor_highlight-selector.js] [browser_styleeditor_import.js] [browser_styleeditor_import_rule.js] [browser_styleeditor_init.js] [browser_styleeditor_inline_friendly_names.js] @@ -78,16 +77,15 @@ skip-if = e10s # Bug 1055333 - style edi [browser_styleeditor_reload.js] [browser_styleeditor_scroll.js] [browser_styleeditor_sv_keynav.js] [browser_styleeditor_sv_resize.js] [browser_styleeditor_selectstylesheet.js] [browser_styleeditor_sourcemaps.js] [browser_styleeditor_sourcemap_large.js] [browser_styleeditor_sourcemap_watching.js] -skip-if = e10s # Bug 1055333 - style editor tests disabled with e10s [browser_styleeditor_sync.js] [browser_styleeditor_syncAddRule.js] [browser_styleeditor_syncAlreadyOpen.js] [browser_styleeditor_syncEditSelector.js] [browser_styleeditor_syncIntoRuleView.js] [browser_styleeditor_transition_rule.js] [browser_styleeditor_xul.js]
--- a/devtools/client/styleeditor/test/browser_styleeditor_sourcemap_watching.js +++ b/devtools/client/styleeditor/test/browser_styleeditor_sourcemap_watching.js @@ -1,98 +1,92 @@ /* vim: set ts=2 et sw=2 tw=80: */ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; -Components.utils.import("resource://gre/modules/Task.jsm"); -var {require} = Components.utils.import("resource://devtools/shared/Loader.jsm", {}); -var promise = require("promise"); - const TESTCASE_URI_HTML = TEST_BASE_HTTP + "sourcemaps-watching.html"; const TESTCASE_URI_CSS = TEST_BASE_HTTP + "sourcemap-css/sourcemaps.css"; const TESTCASE_URI_REG_CSS = TEST_BASE_HTTP + "simple.css"; const TESTCASE_URI_SCSS = TEST_BASE_HTTP + "sourcemap-sass/sourcemaps.scss"; const TESTCASE_URI_MAP = TEST_BASE_HTTP + "sourcemap-css/sourcemaps.css.map"; const TESTCASE_SCSS_NAME = "sourcemaps.scss"; const TRANSITIONS_PREF = "devtools.styleeditor.transitions"; const CSS_TEXT = "* { color: blue }"; -var Cc = Components.classes; -var Ci = Components.interfaces; +const Cc = Components.classes; +const Ci = Components.interfaces; + +const {FileUtils} = Components.utils.import("resource://gre/modules/FileUtils.jsm", {}); +const {NetUtil} = Components.utils.import("resource://gre/modules/NetUtil.jsm", {}); -var tempScope = {}; -Components.utils.import("resource://gre/modules/FileUtils.jsm", tempScope); -Components.utils.import("resource://gre/modules/NetUtil.jsm", tempScope); -var FileUtils = tempScope.FileUtils; -var NetUtil = tempScope.NetUtil; - -function test() { - waitForExplicitFinish(); - - Services.prefs.setBoolPref(TRANSITIONS_PREF, false); +add_task(function*() { + yield new Promise(resolve => { + SpecialPowers.pushPrefEnv({"set": [ + [TRANSITIONS_PREF, false] + ]}, resolve); + }); - Task.spawn(function*() { - // copy all our files over so we don't screw them up for other tests - let HTMLFile = yield copy(TESTCASE_URI_HTML, ["sourcemaps.html"]); - let CSSFile = yield copy(TESTCASE_URI_CSS, - ["sourcemap-css", "sourcemaps.css"]); - yield copy(TESTCASE_URI_SCSS, ["sourcemap-sass", "sourcemaps.scss"]); - yield copy(TESTCASE_URI_MAP, ["sourcemap-css", "sourcemaps.css.map"]); - yield copy(TESTCASE_URI_REG_CSS, ["simple.css"]); + // copy all our files over so we don't screw them up for other tests + let HTMLFile = yield copy(TESTCASE_URI_HTML, ["sourcemaps.html"]); + let CSSFile = yield copy(TESTCASE_URI_CSS, + ["sourcemap-css", "sourcemaps.css"]); + yield copy(TESTCASE_URI_SCSS, ["sourcemap-sass", "sourcemaps.scss"]); + yield copy(TESTCASE_URI_MAP, ["sourcemap-css", "sourcemaps.css.map"]); + yield copy(TESTCASE_URI_REG_CSS, ["simple.css"]); - let uri = Services.io.newFileURI(HTMLFile); - let testcaseURI = uri.resolve(""); + let uri = Services.io.newFileURI(HTMLFile); + let testcaseURI = uri.resolve(""); + + let { ui } = yield openStyleEditorForURL(testcaseURI); - let { ui } = yield openStyleEditorForURL(testcaseURI); + let editor = ui.editors[1]; + if (getStylesheetNameFor(editor) != TESTCASE_SCSS_NAME) { + editor = ui.editors[2]; + } - let editor = ui.editors[1]; - if (getStylesheetNameFor(editor) != TESTCASE_SCSS_NAME) { - editor = ui.editors[2]; - } + is(getStylesheetNameFor(editor), TESTCASE_SCSS_NAME, "found scss editor"); - is(getStylesheetNameFor(editor), TESTCASE_SCSS_NAME, "found scss editor"); + let link = getLinkFor(editor); + link.click(); + + yield editor.getSourceEditor(); - let link = getLinkFor(editor); - link.click(); - - yield editor.getSourceEditor(); + let element = content.document.querySelector("div"); + let style = content.getComputedStyle(element, null); - let element = content.document.querySelector("div"); - let style = content.getComputedStyle(element, null); + is(style.color, "rgb(255, 0, 102)", "div is red before saving file"); - is(style.color, "rgb(255, 0, 102)", "div is red before saving file"); + // let styleApplied = promise.defer(); + let styleApplied = editor.once("style-applied"); - editor.styleSheet.relatedStyleSheet.once("style-applied", function() { - is(style.color, "rgb(0, 0, 255)", "div is blue after saving file"); - finishUp(); - }); + yield pauseForTimeChange(); - yield pauseForTimeChange(); + // Edit and save Sass in the editor. This will start off a file-watching + // process waiting for the CSS file to change. + yield editSCSS(editor); - // Edit and save Sass in the editor. This will start off a file-watching - // process waiting for the CSS file to change. - yield editSCSS(editor); + // We can't run Sass or another compiler, so we fake it by just + // directly changing the CSS file. + yield editCSSFile(CSSFile); - // We can't run Sass or another compiler, so we fake it by just - // directly changing the CSS file. - yield editCSSFile(CSSFile); + info("wrote to CSS file, waiting for style-applied event"); - info("wrote to CSS file"); - }); -} + yield styleApplied; + + is(style.color, "rgb(0, 0, 255)", "div is blue after saving file"); +}); function editSCSS(editor) { let deferred = promise.defer(); - let pos = {line: 0, ch: 0}; - editor.sourceEditor.replaceText(CSS_TEXT, pos, pos); + editor.sourceEditor.setText(CSS_TEXT); editor.saveToFile(null, function(file) { ok(file, "Scss file should be saved"); deferred.resolve(); }); return deferred.promise; } @@ -107,21 +101,16 @@ function pauseForTimeChange() { // We have to wait for the system time to turn over > 1000 ms so that // our file's last change time will show a change. This reflects what // would happen in real life with a user manually saving the file. setTimeout(deferred.resolve, 2000); return deferred.promise; } -function finishUp() { - Services.prefs.clearUserPref(TRANSITIONS_PREF); - finish(); -} - /* Helpers */ function getLinkFor(editor) { return editor.summary.querySelector(".stylesheet-name"); } function getStylesheetNameFor(editor) { return editor.summary.querySelector(".stylesheet-name > label")
--- a/devtools/client/themes/images/sort-arrows.svg +++ b/devtools/client/themes/images/sort-arrows.svg @@ -1,9 +1,12 @@ <!-- This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -<svg xmlns="http://www.w3.org/2000/svg" width="22" height="11"> - <g fill="#edf0f1" fill-opacity="0.8"> - <polygon points="3,5 5.5,8 8,5"/> - <polygon points="14,7 16.5,4 19,7"/> - </g> +<svg xmlns="http://www.w3.org/2000/svg" width="7" height="4" fill="#edf0f1" fill-opacity="0.8"> + <style> + polygon:not(:target) { + display: none; + } + </style> + <polygon points="0,4 3.5,0 7,4" id="ascending"/> + <polygon points="0,0 3.5,4 7,0" id="descending"/> </svg>
--- a/devtools/client/themes/layoutview.css +++ b/devtools/client/themes/layoutview.css @@ -1,17 +1,17 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/ */ .theme-sidebar { box-sizing: border-box; } -body { +.theme-sidebar body { /* The view will grow bigger as the window gets resized, until 400px */ max-width: 400px; margin: 0px auto; padding: 0; /* "Contain" the absolutely positioned #main element */ position: relative; }
--- a/devtools/client/themes/netmonitor.css +++ b/devtools/client/themes/netmonitor.css @@ -78,29 +78,24 @@ #requests-menu-waterfall-image { display: -moz-box; height: 4px; margin-inline-end: 6px; -moz-box-ordinal-group: 2; width: 7px; } -.requests-menu-header-button[sorted] > .button-box > .button-icon, -.requests-menu-header-button[sorted] #requests-menu-waterfall-image { - list-style-image: url('chrome://devtools/skin/images/sort-arrows.svg'); -} - .requests-menu-header-button[sorted=ascending] > .button-box > .button-icon, .requests-menu-header-button[sorted=ascending] #requests-menu-waterfall-image { - -moz-image-region: rect(4px, 19px, 7px, 14px); + list-style-image: url("chrome://devtools/skin/images/sort-arrows.svg#ascending"); } .requests-menu-header-button[sorted=descending] > .button-box > .button-icon, .requests-menu-header-button[sorted=descending] #requests-menu-waterfall-image { - -moz-image-region: rect(5px, 8px, 8px, 3px); + list-style-image: url("chrome://devtools/skin/images/sort-arrows.svg#descending"); } .requests-menu-header-button > .button-box > .button-text, #requests-menu-waterfall-label-wrapper { -moz-box-flex: 1; } .requests-menu-header-button[sorted], @@ -111,24 +106,29 @@ .requests-menu-header-button[sorted], .requests-menu-header[active] + .requests-menu-header .requests-menu-header-button { border-image: linear-gradient(var(--theme-splitter-color), var(--theme-splitter-color)) 1 1; } /* Network requests table: specific column dimensions */ -.requests-menu-status, -.requests-menu-method-box, -.requests-menu-method { +.requests-menu-status { max-width: 6em; text-align: center; width: 10vw; } +.requests-menu-method, +.requests-menu-method-box { + max-width: 7em; + text-align: center; + width: 10vw; +} + .requests-menu-icon-and-file { width: 22vw; } .requests-menu-icon { background: #fff; width: calc(1em + 4px); height: calc(1em + 4px); @@ -167,18 +167,23 @@ list-style-image: url(chrome://devtools/skin/images/security-state-broken.svg); } .security-state-local { list-style-image: url(chrome://devtools/skin/images/security-state-local.svg); } .requests-menu-type, -.requests-menu-transferred, .requests-menu-size { + max-width: 6em; + text-align: center; + width: 8vw; +} + +.requests-menu-transferred { max-width: 8em; text-align: center; width: 8vw; } /* Network requests table: status codes */ .requests-menu-status-code {
--- a/devtools/client/themes/widgets.css +++ b/devtools/client/themes/widgets.css @@ -1204,71 +1204,73 @@ /* Column Headers */ .table-widget-column-header, .table-widget-cell { -moz-border-end: 1px solid var(--table-splitter-color) !important; } /* Table widget column header colors are taken from netmonitor.inc.css to match - the look of both the tables. This needs to be updated along with netmonitor - header colors in bug 951714 */ + the look of both the tables. */ .table-widget-column-header { - background: rgba(0,0,0,0); position: sticky; top: 0; width: 100%; + margin: 0; padding: 5px 0 0 !important; color: inherit; text-align: center; font-weight: inherit !important; - transition: background-color 0.1s ease-in-out; + border-bottom-width: 0 !important; + border-image: linear-gradient(transparent 15%, var(--theme-splitter-color) 15%, var(--theme-splitter-color) 85%, transparent 85%) 1 1; + background-repeat: no-repeat; } -.table-widget-column-header:hover { - background-image: linear-gradient(rgba(0,0,0,0.10), rgba(0,0,0,0.10)); -} - -.table-widget-column-header:hover:active { - background-image: linear-gradient(rgba(0,0,0,0.25), rgba(0,0,0,0.25)); +.table-widget-column-header:not([sorted]):hover { + background-image: linear-gradient(rgba(0,0,0,0.1),rgba(0,0,0,0.1)); } -.table-widget-column-header:not(:active)[sorted] { - background-image: linear-gradient(rgba(0,0,0,0.15), rgba(0,0,0,0.15)); +.table-widget-column-header[sorted] { + background-color: var(--theme-selection-background); + color: var(--theme-selection-color); + border-image: linear-gradient(var(--theme-splitter-color), var(--theme-splitter-color)) 1 1; + box-shadow: -0.5px 0px 0px 0.5px var(--theme-splitter-color); + background-position: right 6px center; } -.table-widget-column-header:not(:active)[sorted=ascending] { - background-image: radial-gradient(farthest-side at center top, hsla(200,100%,70%,.7), hsla(200,100%,70%,0.3)), - linear-gradient(rgba(0,0,0,0.15), rgba(0,0,0,0.15)); - background-size: 100% 1px, auto; - background-repeat: no-repeat, repeat; +.table-widget-column-header[sorted]:-moz-locale-dir(rtl) { + background-position: 6px center; } -.table-widget-column-header:not(:active)[sorted=descending] { - background-image: radial-gradient(farthest-side at center bottom, hsla(200,100%,70%,.7), hsla(200,100%,70%,0.3)), - linear-gradient(rgba(0,0,0,0.15), rgba(0,0,0,0.15)); - background-size: 100% 1px, auto; - background-repeat: no-repeat, repeat; - background-position: bottom; +.table-widget-column-header[sorted=ascending] { + background-image: url("chrome://devtools/skin/images/sort-arrows.svg#ascending"); +} + +.table-widget-column-header[sorted=descending] { + background-image: url("chrome://devtools/skin/images/sort-arrows.svg#descending"); } /* Cells */ .table-widget-cell { width: 100%; padding: 3px 4px; background-clip: padding-box; min-width: 100px; -moz-user-focus: normal; margin-bottom: -1px !important; border-bottom: 1px solid transparent; color: var(--theme-body-color); } +.table-widget-column-header + .table-widget-cell { + border-top: 1px solid var(--theme-splitter-color); +} + .table-widget-cell:last-child { border-bottom: 1px solid var(--table-splitter-color); } :root:not(.filtering) .table-widget-cell:nth-child(odd):not(.theme-selected), .table-widget-cell:not(.theme-selected)[odd] { background: var(--table-zebra-background); }
--- a/devtools/server/actors/canvas.js +++ b/devtools/server/actors/canvas.js @@ -4,16 +4,17 @@ "use strict"; const {Cc, Ci, Cu, Cr} = require("chrome"); const events = require("sdk/event/core"); const promise = require("promise"); const protocol = require("devtools/server/protocol"); const {CallWatcherActor, CallWatcherFront} = require("devtools/server/actors/call-watcher"); const DevToolsUtils = require("devtools/shared/DevToolsUtils"); +const {WebGLPrimitiveCounter} = require("devtools/server/primitive"); const {on, once, off, emit} = events; const {method, custom, Arg, Option, RetVal} = protocol; const CANVAS_CONTEXTS = [ "CanvasRenderingContext2D", "WebGLRenderingContext" ]; @@ -108,31 +109,38 @@ var FrameSnapshotActor = protocol.ActorC * The server connection. * @param HTMLCanvasElement canvas * A reference to the content canvas. * @param array calls * An array of "function-call" actor instances. * @param object screenshot * A single "snapshot-image" type instance. */ - initialize: function(conn, { canvas, calls, screenshot }) { + initialize: function(conn, { canvas, calls, screenshot, primitive }) { protocol.Actor.prototype.initialize.call(this, conn); this._contentCanvas = canvas; this._functionCalls = calls; this._animationFrameEndScreenshot = screenshot; + this._primitive = primitive; }, /** * Gets as much data about this snapshot without computing anything costly. */ getOverview: method(function() { return { calls: this._functionCalls, thumbnails: this._functionCalls.map(e => e._thumbnail).filter(e => !!e), - screenshot: this._animationFrameEndScreenshot + screenshot: this._animationFrameEndScreenshot, + primitive: { + tris: this._primitive.tris, + vertices: this._primitive.vertices, + points: this._primitive.points, + lines: this._primitive.lines + } }; }, { response: { overview: RetVal("snapshot-overview") } }), /** * Gets a screenshot of the canvas's contents after the specified * function was called. @@ -236,20 +244,22 @@ var CanvasActor = exports.CanvasActor = // Reset for each recording, boolean indicating whether or not // any draw calls were called for a recording. _animationContainsDrawCall: false, typeName: "canvas", initialize: function(conn, tabActor) { protocol.Actor.prototype.initialize.call(this, conn); this.tabActor = tabActor; + this._webGLPrimitiveCounter = new WebGLPrimitiveCounter(tabActor); this._onContentFunctionCall = this._onContentFunctionCall.bind(this); }, destroy: function(conn) { protocol.Actor.prototype.destroy.call(this, conn); + this._webGLPrimitiveCounter.destroy(); this.finalize(); }, /** * Starts listening for function calls. */ setup: method(function({ reload }) { if (this._initialized) { @@ -312,16 +322,17 @@ var CanvasActor = exports.CanvasActor = recordAnimationFrame: method(function() { if (this._callWatcher.isRecording()) { return this._currentAnimationFrameSnapshot.promise; } this._recordingContainsDrawCall = false; this._callWatcher.eraseRecording(); this._callWatcher.initFrameStartTimestamp(); + this._webGLPrimitiveCounter.resetCounts(); this._callWatcher.resumeRecording(); let deferred = this._currentAnimationFrameSnapshot = promise.defer(); return deferred.promise; }, { response: { snapshot: RetVal("nullable:frame-snapshot") } }), @@ -365,16 +376,17 @@ var CanvasActor = exports.CanvasActor = // those timers is considered extremely poor practice, they're still widely // used on the web, especially for old demos; it's nice to support them as well. if (CanvasFront.LOOP_GENERATORS.has(name)) { this._handleAnimationFrame(functionCall); return; } if (CanvasFront.DRAW_CALLS.has(name) && this._animationStarted) { this._handleDrawCall(functionCall); + this._webGLPrimitiveCounter.handleDrawPrimitive(functionCall); return; } }, /** * Handle animations generated using requestAnimationFrame. */ _handleAnimationFrame: function(functionCall) { @@ -410,31 +422,38 @@ var CanvasActor = exports.CanvasActor = // Since the animation frame finished, get a hold of the (already retrieved) // canvas pixels to conveniently create a screenshot of the final rendering. let index = this._lastDrawCallIndex; let width = this._lastContentCanvasWidth; let height = this._lastContentCanvasHeight; let flipped = !!this._lastThumbnailFlipped; // undefined -> false let pixels = ContextUtils.getPixelStorage()["8bit"]; + let primitiveResult = this._webGLPrimitiveCounter.getCounts(); let animationFrameEndScreenshot = { index: index, width: width, height: height, scaling: 1, flipped: flipped, pixels: pixels.subarray(0, width * height * 4) }; // Wrap the function calls and screenshot in a FrameSnapshotActor instance, // which will resolve the promise returned by `recordAnimationFrame`. let frameSnapshot = new FrameSnapshotActor(this.conn, { canvas: this._lastDrawCallCanvas, calls: functionCalls, - screenshot: animationFrameEndScreenshot + screenshot: animationFrameEndScreenshot, + primitive: { + tris: primitiveResult.tris, + vertices: primitiveResult.vertices, + points: primitiveResult.points, + lines: primitiveResult.lines + } }); this._currentAnimationFrameSnapshot.resolve(frameSnapshot); this._currentAnimationFrameSnapshot = null; this._animationStarted = false; }, /**
--- a/devtools/server/moz.build +++ b/devtools/server/moz.build @@ -28,14 +28,15 @@ SOURCES += [ FINAL_LIBRARY = 'xul' DevToolsModules( 'child.js', 'content-globals.js', 'content-server.jsm', 'main.js', + 'primitive.js', 'protocol.js', 'worker.js' ) if CONFIG['GNU_CXX']: CXXFLAGS += ['-Wshadow']
new file mode 100644 --- /dev/null +++ b/devtools/server/primitive.js @@ -0,0 +1,165 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +const { on, once, off, emit } = require("sdk/event/core"); +const { Class } = require("sdk/core/heritage"); + +const WebGLPrimitivesType = { + "POINTS": 0, + "LINES": 1, + "LINE_LOOP": 2, + "LINE_STRIP": 3, + "TRIANGLES": 4, + "TRIANGLE_STRIP": 5, + "TRIANGLE_FAN": 6 +}; + +/** + * A utility for monitoring WebGL primitive draws. Takes a `tabActor` + * and monitors primitive draws over time. + */ +const WebGLDrawArrays = "drawArrays"; +const WebGLDrawElements = "drawElements"; + +var WebGLPrimitiveCounter = exports.WebGLPrimitiveCounter = Class({ + initialize: function(tabActor) { + this.tabActor = tabActor; + }, + + destroy: function() { + this.stopRecording(); + }, + + /** + * Starts monitoring primitive draws, storing the primitives count per tick. + */ + resetCounts: function() { + this._tris = 0; + this._vertices = 0; + this._points = 0; + this._lines = 0; + this._startTime = this.tabActor.docShell.now(); + }, + + /** + * Stops monitoring primitive draws, returning the recorded values. + */ + getCounts: function() { + var result = { + tris: this._tris, + vertices: this._vertices, + points: this._points, + lines: this._lines + }; + + this._tris = 0; + this._vertices = 0; + this._points = 0; + this._lines = 0; + return result; + }, + + /** + * Handles WebGL draw primitive functions to catch primitive info. + */ + handleDrawPrimitive: function(functionCall) { + let { name, args } = functionCall.details; + + if (name === WebGLDrawArrays) { + this._processDrawArrays(args); + } else if (name === WebGLDrawElements) { + this._processDrawElements(args); + } + }, + + /** + * Processes WebGL drawArrays method to count primitve numbers + */ + _processDrawArrays: function(args) { + let mode = args[0]; + let count = args[2]; + + switch ( mode ) { + case WebGLPrimitivesType.POINTS: + this._vertices += count; + this._points += count; + break; + case WebGLPrimitivesType.LINES: + this._vertices += count; + this._lines += (count / 2); + break; + case WebGLPrimitivesType.LINE_LOOP: + this._vertices += count; + this._lines += count; + break; + case WebGLPrimitivesType.LINE_STRIP: + this._vertices += count; + this._lines += (count - 1); + break; + case WebGLPrimitivesType.TRIANGLES: + this._tris += (count / 3); + this._vertices += count; + break; + case WebGLPrimitivesType.TRIANGLE_STRIP: + this._tris += (count - 2); + this._vertices += count; + break; + case WebGLPrimitivesType.TRIANGLE_FAN: + this._tris += (count - 2); + this._vertices += count; + break; + default: + console.error("_processDrawArrays doesn't define this type."); + break; + } + }, + + /** + * Processes WebGL drawElements method to count primitve numbers + */ + _processDrawElements: function(args) { + let mode = args[0]; + let count = args[1]; + + switch ( mode ) { + case WebGLPrimitivesType.POINTS: + this._vertices += count; + this._points += count; + break; + case WebGLPrimitivesType.LINES: + this._vertices += count; + this._lines += (count / 2); + break; + case WebGLPrimitivesType.LINE_LOOP: + this._vertices += count; + this._lines += count; + break; + case WebGLPrimitivesType.LINE_STRIP: + this._vertices += count; + this._lines += (count - 1); + break; + case WebGLPrimitivesType.TRIANGLES: + let tris = count / 3; + let vertex = tris * 3; + + if (tris > 1) { + vertex = tris * 2; + } + this._tris += tris; + this._vertices += vertex; + break; + case WebGLPrimitivesType.TRIANGLE_STRIP: + this._tris += (count - 2); + this._vertices += count; + break; + case WebGLPrimitivesType.TRIANGLE_FAN: + this._tris += (count - 2); + this._vertices += count; + default: + console.error("_processDrawElements doesn't define this type."); + break; + } + } +}); \ No newline at end of file
--- a/devtools/shared/acorn/UPGRADING.md +++ b/devtools/shared/acorn/UPGRADING.md @@ -21,8 +21,11 @@ 3. Copy acorn.js to our tree: 4. Copy acorn_loose.js to our tree: $ cp acorn_loose.js /path/to/mozilla-central/devtools/shared/acorn/acorn_loose.js 5. Copy util/walk.js to our tree: $ cp util/walk.js /path/to/mozilla-central/devtools/shared/acorn/walk.js + +6. Check and see if javascript pretty-printing and scratchpad work without any errors. As of version 2.6.4 we need to comment out lines in acorn_loose.js that attempt to extend the acorn object, like `acorn.parse_dammit = parse_dammit`. +
--- a/devtools/shared/acorn/acorn.js +++ b/devtools/shared/acorn/acorn.js @@ -1,2696 +1,3330 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.acorn = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ +// A recursive descent parser operates by defining functions for all +// syntactic elements, and recursively calling those, each function +// advancing the input stream and returning an AST node. Precedence +// of constructs (for example, the fact that `!x[1]` means `!(x[1])` +// instead of `(!x)[1]` is handled by the fact that the parser +// function that parses unary prefix operators is called first, and +// in turn calls the function that parses `[]` subscripts — that +// way, it'll receive the node for `x[1]` already parsed, and wraps +// *that* in the unary operator node. +// +// Acorn uses an [operator precedence parser][opp] to handle binary +// operator precedence, because it is much more compact than using +// the technique outlined above, which uses different, nesting +// functions to specify precedence, for all of the ten binary +// precedence levels that JavaScript defines. +// +// [opp]: http://en.wikipedia.org/wiki/Operator-precedence_parser + +"use strict"; + +var _tokentype = _dereq_("./tokentype"); + +var _state = _dereq_("./state"); + +var pp = _state.Parser.prototype; + +// Check if property name clashes with already added. +// Object/class getters and setters are not allowed to clash — +// either with each other or with an init property — and in +// strict mode, init properties are also not allowed to be repeated. + +pp.checkPropClash = function (prop, propHash) { + if (this.options.ecmaVersion >= 6 && (prop.computed || prop.method || prop.shorthand)) return; + var key = prop.key;var name = undefined; + switch (key.type) { + case "Identifier": + name = key.name;break; + case "Literal": + name = String(key.value);break; + default: + return; + } + var kind = prop.kind; + + if (this.options.ecmaVersion >= 6) { + if (name === "__proto__" && kind === "init") { + if (propHash.proto) this.raise(key.start, "Redefinition of __proto__ property"); + propHash.proto = true; + } + return; + } + name = "$" + name; + var other = propHash[name]; + if (other) { + var isGetSet = kind !== "init"; + if ((this.strict || isGetSet) && other[kind] || !(isGetSet ^ other.init)) this.raise(key.start, "Redefinition of property"); + } else { + other = propHash[name] = { + init: false, + get: false, + set: false + }; + } + other[kind] = true; +}; + +// ### Expression parsing + +// These nest, from the most general expression type at the top to +// 'atomic', nondivisible expression types at the bottom. Most of +// the functions will simply let the function(s) below them parse, +// and, *if* the syntactic construct they handle is present, wrap +// the AST node that the inner parser gave them in another node. + +// Parse a full expression. The optional arguments are used to +// forbid the `in` operator (in for loops initalization expressions) +// and provide reference for storing '=' operator inside shorthand +// property assignment in contexts where both object expression +// and object pattern might appear (so it's possible to raise +// delayed syntax error at correct position). + +pp.parseExpression = function (noIn, refDestructuringErrors) { + var startPos = this.start, + startLoc = this.startLoc; + var expr = this.parseMaybeAssign(noIn, refDestructuringErrors); + if (this.type === _tokentype.types.comma) { + var node = this.startNodeAt(startPos, startLoc); + node.expressions = [expr]; + while (this.eat(_tokentype.types.comma)) node.expressions.push(this.parseMaybeAssign(noIn, refDestructuringErrors)); + return this.finishNode(node, "SequenceExpression"); + } + return expr; +}; + +// Parse an assignment expression. This includes applications of +// operators like `+=`. + +pp.parseMaybeAssign = function (noIn, refDestructuringErrors, afterLeftParse) { + if (this.type == _tokentype.types._yield && this.inGenerator) return this.parseYield(); + + var validateDestructuring = false; + if (!refDestructuringErrors) { + refDestructuringErrors = { shorthandAssign: 0, trailingComma: 0 }; + validateDestructuring = true; + } + var startPos = this.start, + startLoc = this.startLoc; + if (this.type == _tokentype.types.parenL || this.type == _tokentype.types.name) this.potentialArrowAt = this.start; + var left = this.parseMaybeConditional(noIn, refDestructuringErrors); + if (afterLeftParse) left = afterLeftParse.call(this, left, startPos, startLoc); + if (this.type.isAssign) { + if (validateDestructuring) this.checkPatternErrors(refDestructuringErrors, true); + var node = this.startNodeAt(startPos, startLoc); + node.operator = this.value; + node.left = this.type === _tokentype.types.eq ? this.toAssignable(left) : left; + refDestructuringErrors.shorthandAssign = 0; // reset because shorthand default was used correctly + this.checkLVal(left); + this.next(); + node.right = this.parseMaybeAssign(noIn); + return this.finishNode(node, "AssignmentExpression"); + } else { + if (validateDestructuring) this.checkExpressionErrors(refDestructuringErrors, true); + } + return left; +}; + +// Parse a ternary conditional (`?:`) operator. + +pp.parseMaybeConditional = function (noIn, refDestructuringErrors) { + var startPos = this.start, + startLoc = this.startLoc; + var expr = this.parseExprOps(noIn, refDestructuringErrors); + if (this.checkExpressionErrors(refDestructuringErrors)) return expr; + if (this.eat(_tokentype.types.question)) { + var node = this.startNodeAt(startPos, startLoc); + node.test = expr; + node.consequent = this.parseMaybeAssign(); + this.expect(_tokentype.types.colon); + node.alternate = this.parseMaybeAssign(noIn); + return this.finishNode(node, "ConditionalExpression"); + } + return expr; +}; + +// Start the precedence parser. + +pp.parseExprOps = function (noIn, refDestructuringErrors) { + var startPos = this.start, + startLoc = this.startLoc; + var expr = this.parseMaybeUnary(refDestructuringErrors); + if (this.checkExpressionErrors(refDestructuringErrors)) return expr; + return this.parseExprOp(expr, startPos, startLoc, -1, noIn); +}; + +// Parse binary operators with the operator precedence parsing +// algorithm. `left` is the left-hand side of the operator. +// `minPrec` provides context that allows the function to stop and +// defer further parser to one of its callers when it encounters an +// operator that has a lower precedence than the set it is parsing. + +pp.parseExprOp = function (left, leftStartPos, leftStartLoc, minPrec, noIn) { + var prec = this.type.binop; + if (prec != null && (!noIn || this.type !== _tokentype.types._in)) { + if (prec > minPrec) { + var node = this.startNodeAt(leftStartPos, leftStartLoc); + node.left = left; + node.operator = this.value; + var op = this.type; + this.next(); + var startPos = this.start, + startLoc = this.startLoc; + node.right = this.parseExprOp(this.parseMaybeUnary(), startPos, startLoc, prec, noIn); + this.finishNode(node, op === _tokentype.types.logicalOR || op === _tokentype.types.logicalAND ? "LogicalExpression" : "BinaryExpression"); + return this.parseExprOp(node, leftStartPos, leftStartLoc, minPrec, noIn); + } + } + return left; +}; + +// Parse unary operators, both prefix and postfix. + +pp.parseMaybeUnary = function (refDestructuringErrors) { + if (this.type.prefix) { + var node = this.startNode(), + update = this.type === _tokentype.types.incDec; + node.operator = this.value; + node.prefix = true; + this.next(); + node.argument = this.parseMaybeUnary(); + this.checkExpressionErrors(refDestructuringErrors, true); + if (update) this.checkLVal(node.argument);else if (this.strict && node.operator === "delete" && node.argument.type === "Identifier") this.raise(node.start, "Deleting local variable in strict mode"); + return this.finishNode(node, update ? "UpdateExpression" : "UnaryExpression"); + } + var startPos = this.start, + startLoc = this.startLoc; + var expr = this.parseExprSubscripts(refDestructuringErrors); + if (this.checkExpressionErrors(refDestructuringErrors)) return expr; + while (this.type.postfix && !this.canInsertSemicolon()) { + var node = this.startNodeAt(startPos, startLoc); + node.operator = this.value; + node.prefix = false; + node.argument = expr; + this.checkLVal(expr); + this.next(); + expr = this.finishNode(node, "UpdateExpression"); + } + return expr; +}; + +// Parse call, dot, and `[]`-subscript expressions. + +pp.parseExprSubscripts = function (refDestructuringErrors) { + var startPos = this.start, + startLoc = this.startLoc; + var expr = this.parseExprAtom(refDestructuringErrors); + var skipArrowSubscripts = expr.type === "ArrowFunctionExpression" && this.input.slice(this.lastTokStart, this.lastTokEnd) !== ")"; + if (this.checkExpressionErrors(refDestructuringErrors) || skipArrowSubscripts) return expr; + return this.parseSubscripts(expr, startPos, startLoc); +}; + +pp.parseSubscripts = function (base, startPos, startLoc, noCalls) { + for (;;) { + if (this.eat(_tokentype.types.dot)) { + var node = this.startNodeAt(startPos, startLoc); + node.object = base; + node.property = this.parseIdent(true); + node.computed = false; + base = this.finishNode(node, "MemberExpression"); + } else if (this.eat(_tokentype.types.bracketL)) { + var node = this.startNodeAt(startPos, startLoc); + node.object = base; + node.property = this.parseExpression(); + node.computed = true; + this.expect(_tokentype.types.bracketR); + base = this.finishNode(node, "MemberExpression"); + } else if (!noCalls && this.eat(_tokentype.types.parenL)) { + var node = this.startNodeAt(startPos, startLoc); + node.callee = base; + node.arguments = this.parseExprList(_tokentype.types.parenR, false); + base = this.finishNode(node, "CallExpression"); + } else if (this.type === _tokentype.types.backQuote) { + var node = this.startNodeAt(startPos, startLoc); + node.tag = base; + node.quasi = this.parseTemplate(); + base = this.finishNode(node, "TaggedTemplateExpression"); + } else { + return base; + } + } +}; + +// Parse an atomic expression — either a single token that is an +// expression, an expression started by a keyword like `function` or +// `new`, or an expression wrapped in punctuation like `()`, `[]`, +// or `{}`. + +pp.parseExprAtom = function (refDestructuringErrors) { + var node = undefined, + canBeArrow = this.potentialArrowAt == this.start; + switch (this.type) { + case _tokentype.types._super: + if (!this.inFunction) this.raise(this.start, "'super' outside of function or class"); + case _tokentype.types._this: + var type = this.type === _tokentype.types._this ? "ThisExpression" : "Super"; + node = this.startNode(); + this.next(); + return this.finishNode(node, type); + + case _tokentype.types._yield: + if (this.inGenerator) this.unexpected(); + + case _tokentype.types.name: + var startPos = this.start, + startLoc = this.startLoc; + var id = this.parseIdent(this.type !== _tokentype.types.name); + if (canBeArrow && !this.canInsertSemicolon() && this.eat(_tokentype.types.arrow)) return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), [id]); + return id; + + case _tokentype.types.regexp: + var value = this.value; + node = this.parseLiteral(value.value); + node.regex = { pattern: value.pattern, flags: value.flags }; + return node; + + case _tokentype.types.num:case _tokentype.types.string: + return this.parseLiteral(this.value); + + case _tokentype.types._null:case _tokentype.types._true:case _tokentype.types._false: + node = this.startNode(); + node.value = this.type === _tokentype.types._null ? null : this.type === _tokentype.types._true; + node.raw = this.type.keyword; + this.next(); + return this.finishNode(node, "Literal"); + + case _tokentype.types.parenL: + return this.parseParenAndDistinguishExpression(canBeArrow); + + case _tokentype.types.bracketL: + node = this.startNode(); + this.next(); + // check whether this is array comprehension or regular array + if (this.options.ecmaVersion >= 7 && this.type === _tokentype.types._for) { + return this.parseComprehension(node, false); + } + node.elements = this.parseExprList(_tokentype.types.bracketR, true, true, refDestructuringErrors); + return this.finishNode(node, "ArrayExpression"); + + case _tokentype.types.braceL: + return this.parseObj(false, refDestructuringErrors); + + case _tokentype.types._function: + node = this.startNode(); + this.next(); + return this.parseFunction(node, false); + + case _tokentype.types._class: + return this.parseClass(this.startNode(), false); + + case _tokentype.types._new: + return this.parseNew(); + + case _tokentype.types.backQuote: + return this.parseTemplate(); + + default: + this.unexpected(); + } +}; + +pp.parseLiteral = function (value) { + var node = this.startNode(); + node.value = value; + node.raw = this.input.slice(this.start, this.end); + this.next(); + return this.finishNode(node, "Literal"); +}; + +pp.parseParenExpression = function () { + this.expect(_tokentype.types.parenL); + var val = this.parseExpression(); + this.expect(_tokentype.types.parenR); + return val; +}; + +pp.parseParenAndDistinguishExpression = function (canBeArrow) { + var startPos = this.start, + startLoc = this.startLoc, + val = undefined; + if (this.options.ecmaVersion >= 6) { + this.next(); + + if (this.options.ecmaVersion >= 7 && this.type === _tokentype.types._for) { + return this.parseComprehension(this.startNodeAt(startPos, startLoc), true); + } + + var innerStartPos = this.start, + innerStartLoc = this.startLoc; + var exprList = [], + first = true; + var refDestructuringErrors = { shorthandAssign: 0, trailingComma: 0 }, + spreadStart = undefined, + innerParenStart = undefined; + while (this.type !== _tokentype.types.parenR) { + first ? first = false : this.expect(_tokentype.types.comma); + if (this.type === _tokentype.types.ellipsis) { + spreadStart = this.start; + exprList.push(this.parseParenItem(this.parseRest())); + break; + } else { + if (this.type === _tokentype.types.parenL && !innerParenStart) { + innerParenStart = this.start; + } + exprList.push(this.parseMaybeAssign(false, refDestructuringErrors, this.parseParenItem)); + } + } + var innerEndPos = this.start, + innerEndLoc = this.startLoc; + this.expect(_tokentype.types.parenR); + + if (canBeArrow && !this.canInsertSemicolon() && this.eat(_tokentype.types.arrow)) { + this.checkPatternErrors(refDestructuringErrors, true); + if (innerParenStart) this.unexpected(innerParenStart); + return this.parseParenArrowList(startPos, startLoc, exprList); + } + + if (!exprList.length) this.unexpected(this.lastTokStart); + if (spreadStart) this.unexpected(spreadStart); + this.checkExpressionErrors(refDestructuringErrors, true); + + if (exprList.length > 1) { + val = this.startNodeAt(innerStartPos, innerStartLoc); + val.expressions = exprList; + this.finishNodeAt(val, "SequenceExpression", innerEndPos, innerEndLoc); + } else { + val = exprList[0]; + } + } else { + val = this.parseParenExpression(); + } + + if (this.options.preserveParens) { + var par = this.startNodeAt(startPos, startLoc); + par.expression = val; + return this.finishNode(par, "ParenthesizedExpression"); + } else { + return val; + } +}; + +pp.parseParenItem = function (item) { + return item; +}; + +pp.parseParenArrowList = function (startPos, startLoc, exprList) { + return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), exprList); +}; + +// New's precedence is slightly tricky. It must allow its argument +// to be a `[]` or dot subscript expression, but not a call — at +// least, not without wrapping it in parentheses. Thus, it uses the + +var empty = []; + +pp.parseNew = function () { + var node = this.startNode(); + var meta = this.parseIdent(true); + if (this.options.ecmaVersion >= 6 && this.eat(_tokentype.types.dot)) { + node.meta = meta; + node.property = this.parseIdent(true); + if (node.property.name !== "target") this.raise(node.property.start, "The only valid meta property for new is new.target"); + if (!this.inFunction) this.raise(node.start, "new.target can only be used in functions"); + return this.finishNode(node, "MetaProperty"); + } + var startPos = this.start, + startLoc = this.startLoc; + node.callee = this.parseSubscripts(this.parseExprAtom(), startPos, startLoc, true); + if (this.eat(_tokentype.types.parenL)) node.arguments = this.parseExprList(_tokentype.types.parenR, false);else node.arguments = empty; + return this.finishNode(node, "NewExpression"); +}; + +// Parse template expression. + +pp.parseTemplateElement = function () { + var elem = this.startNode(); + elem.value = { + raw: this.input.slice(this.start, this.end).replace(/\r\n?/g, '\n'), + cooked: this.value + }; + this.next(); + elem.tail = this.type === _tokentype.types.backQuote; + return this.finishNode(elem, "TemplateElement"); +}; + +pp.parseTemplate = function () { + var node = this.startNode(); + this.next(); + node.expressions = []; + var curElt = this.parseTemplateElement(); + node.quasis = [curElt]; + while (!curElt.tail) { + this.expect(_tokentype.types.dollarBraceL); + node.expressions.push(this.parseExpression()); + this.expect(_tokentype.types.braceR); + node.quasis.push(curElt = this.parseTemplateElement()); + } + this.next(); + return this.finishNode(node, "TemplateLiteral"); +}; + +// Parse an object literal or binding pattern. + +pp.parseObj = function (isPattern, refDestructuringErrors) { + var node = this.startNode(), + first = true, + propHash = {}; + node.properties = []; + this.next(); + while (!this.eat(_tokentype.types.braceR)) { + if (!first) { + this.expect(_tokentype.types.comma); + if (this.afterTrailingComma(_tokentype.types.braceR)) break; + } else first = false; + + var prop = this.startNode(), + isGenerator = undefined, + startPos = undefined, + startLoc = undefined; + if (this.options.ecmaVersion >= 6) { + prop.method = false; + prop.shorthand = false; + if (isPattern || refDestructuringErrors) { + startPos = this.start; + startLoc = this.startLoc; + } + if (!isPattern) isGenerator = this.eat(_tokentype.types.star); + } + this.parsePropertyName(prop); + this.parsePropertyValue(prop, isPattern, isGenerator, startPos, startLoc, refDestructuringErrors); + this.checkPropClash(prop, propHash); + node.properties.push(this.finishNode(prop, "Property")); + } + return this.finishNode(node, isPattern ? "ObjectPattern" : "ObjectExpression"); +}; + +pp.parsePropertyValue = function (prop, isPattern, isGenerator, startPos, startLoc, refDestructuringErrors) { + if (this.eat(_tokentype.types.colon)) { + prop.value = isPattern ? this.parseMaybeDefault(this.start, this.startLoc) : this.parseMaybeAssign(false, refDestructuringErrors); + prop.kind = "init"; + } else if (this.options.ecmaVersion >= 6 && this.type === _tokentype.types.parenL) { + if (isPattern) this.unexpected(); + prop.kind = "init"; + prop.method = true; + prop.value = this.parseMethod(isGenerator); + } else if (this.options.ecmaVersion >= 5 && !prop.computed && prop.key.type === "Identifier" && (prop.key.name === "get" || prop.key.name === "set") && (this.type != _tokentype.types.comma && this.type != _tokentype.types.braceR)) { + if (isGenerator || isPattern) this.unexpected(); + prop.kind = prop.key.name; + this.parsePropertyName(prop); + prop.value = this.parseMethod(false); + var paramCount = prop.kind === "get" ? 0 : 1; + if (prop.value.params.length !== paramCount) { + var start = prop.value.start; + if (prop.kind === "get") this.raise(start, "getter should have no params");else this.raise(start, "setter should have exactly one param"); + } + } else if (this.options.ecmaVersion >= 6 && !prop.computed && prop.key.type === "Identifier") { + prop.kind = "init"; + if (isPattern) { + if (this.keywords.test(prop.key.name) || (this.strict ? this.reservedWordsStrictBind : this.reservedWords).test(prop.key.name)) this.raise(prop.key.start, "Binding " + prop.key.name); + prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key); + } else if (this.type === _tokentype.types.eq && refDestructuringErrors) { + if (!refDestructuringErrors.shorthandAssign) refDestructuringErrors.shorthandAssign = this.start; + prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key); + } else { + prop.value = prop.key; + } + prop.shorthand = true; + } else this.unexpected(); +}; + +pp.parsePropertyName = function (prop) { + if (this.options.ecmaVersion >= 6) { + if (this.eat(_tokentype.types.bracketL)) { + prop.computed = true; + prop.key = this.parseMaybeAssign(); + this.expect(_tokentype.types.bracketR); + return prop.key; + } else { + prop.computed = false; + } + } + return prop.key = this.type === _tokentype.types.num || this.type === _tokentype.types.string ? this.parseExprAtom() : this.parseIdent(true); +}; + +// Initialize empty function node. + +pp.initFunction = function (node) { + node.id = null; + if (this.options.ecmaVersion >= 6) { + node.generator = false; + node.expression = false; + } +}; + +// Parse object or class method. + +pp.parseMethod = function (isGenerator) { + var node = this.startNode(); + this.initFunction(node); + this.expect(_tokentype.types.parenL); + node.params = this.parseBindingList(_tokentype.types.parenR, false, false); + if (this.options.ecmaVersion >= 6) node.generator = isGenerator; + this.parseFunctionBody(node, false); + return this.finishNode(node, "FunctionExpression"); +}; + +// Parse arrow function expression with given parameters. + +pp.parseArrowExpression = function (node, params) { + this.initFunction(node); + node.params = this.toAssignableList(params, true); + this.parseFunctionBody(node, true); + return this.finishNode(node, "ArrowFunctionExpression"); +}; + +// Parse function body and check parameters. + +pp.parseFunctionBody = function (node, isArrowFunction) { + var isExpression = isArrowFunction && this.type !== _tokentype.types.braceL; + + if (isExpression) { + node.body = this.parseMaybeAssign(); + node.expression = true; + } else { + // Start a new scope with regard to labels and the `inFunction` + // flag (restore them to their old value afterwards). + var oldInFunc = this.inFunction, + oldInGen = this.inGenerator, + oldLabels = this.labels; + this.inFunction = true;this.inGenerator = node.generator;this.labels = []; + node.body = this.parseBlock(true); + node.expression = false; + this.inFunction = oldInFunc;this.inGenerator = oldInGen;this.labels = oldLabels; + } + + // If this is a strict mode function, verify that argument names + // are not repeated, and it does not try to bind the words `eval` + // or `arguments`. + if (this.strict || !isExpression && node.body.body.length && this.isUseStrict(node.body.body[0])) { + var oldStrict = this.strict; + this.strict = true; + if (node.id) this.checkLVal(node.id, true); + this.checkParams(node); + this.strict = oldStrict; + } else if (isArrowFunction) { + this.checkParams(node); + } +}; + +// Checks function params for various disallowed patterns such as using "eval" +// or "arguments" and duplicate parameters. + +pp.checkParams = function (node) { + var nameHash = {}; + for (var i = 0; i < node.params.length; i++) { + this.checkLVal(node.params[i], true, nameHash); + } +}; + +// Parses a comma-separated list of expressions, and returns them as +// an array. `close` is the token type that ends the list, and +// `allowEmpty` can be turned on to allow subsequent commas with +// nothing in between them to be parsed as `null` (which is needed +// for array literals). + +pp.parseExprList = function (close, allowTrailingComma, allowEmpty, refDestructuringErrors) { + var elts = [], + first = true; + while (!this.eat(close)) { + if (!first) { + this.expect(_tokentype.types.comma); + if (this.type === close && refDestructuringErrors && !refDestructuringErrors.trailingComma) { + refDestructuringErrors.trailingComma = this.lastTokStart; + } + if (allowTrailingComma && this.afterTrailingComma(close)) break; + } else first = false; + + var elt = undefined; + if (allowEmpty && this.type === _tokentype.types.comma) elt = null;else if (this.type === _tokentype.types.ellipsis) elt = this.parseSpread(refDestructuringErrors);else elt = this.parseMaybeAssign(false, refDestructuringErrors); + elts.push(elt); + } + return elts; +}; + +// Parse the next token as an identifier. If `liberal` is true (used +// when parsing properties), it will also convert keywords into +// identifiers. + +pp.parseIdent = function (liberal) { + var node = this.startNode(); + if (liberal && this.options.allowReserved == "never") liberal = false; + if (this.type === _tokentype.types.name) { + if (!liberal && (this.strict ? this.reservedWordsStrict : this.reservedWords).test(this.value) && (this.options.ecmaVersion >= 6 || this.input.slice(this.start, this.end).indexOf("\\") == -1)) this.raise(this.start, "The keyword '" + this.value + "' is reserved"); + node.name = this.value; + } else if (liberal && this.type.keyword) { + node.name = this.type.keyword; + } else { + this.unexpected(); + } + this.next(); + return this.finishNode(node, "Identifier"); +}; + +// Parses yield expression inside generator. + +pp.parseYield = function () { + var node = this.startNode(); + this.next(); + if (this.type == _tokentype.types.semi || this.canInsertSemicolon() || this.type != _tokentype.types.star && !this.type.startsExpr) { + node.delegate = false; + node.argument = null; + } else { + node.delegate = this.eat(_tokentype.types.star); + node.argument = this.parseMaybeAssign(); + } + return this.finishNode(node, "YieldExpression"); +}; + +// Parses array and generator comprehensions. + +pp.parseComprehension = function (node, isGenerator) { + node.blocks = []; + while (this.type === _tokentype.types._for) { + var block = this.startNode(); + this.next(); + this.expect(_tokentype.types.parenL); + block.left = this.parseBindingAtom(); + this.checkLVal(block.left, true); + this.expectContextual("of"); + block.right = this.parseExpression(); + this.expect(_tokentype.types.parenR); + node.blocks.push(this.finishNode(block, "ComprehensionBlock")); + } + node.filter = this.eat(_tokentype.types._if) ? this.parseParenExpression() : null; + node.body = this.parseExpression(); + this.expect(isGenerator ? _tokentype.types.parenR : _tokentype.types.bracketR); + node.generator = isGenerator; + return this.finishNode(node, "ComprehensionExpression"); +}; + +},{"./state":10,"./tokentype":14}],2:[function(_dereq_,module,exports){ +// This is a trick taken from Esprima. It turns out that, on +// non-Chrome browsers, to check whether a string is in a set, a +// predicate containing a big ugly `switch` statement is faster than +// a regular expression, and on Chrome the two are about on par. +// This function uses `eval` (non-lexical) to produce such a +// predicate from a space-separated string of words. +// +// It starts by sorting the words by length. + +// Reserved word lists for various dialects of the language + +"use strict"; + +exports.__esModule = true; +exports.isIdentifierStart = isIdentifierStart; +exports.isIdentifierChar = isIdentifierChar; +var reservedWords = { + 3: "abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile", + 5: "class enum extends super const export import", + 6: "enum", + strict: "implements interface let package private protected public static yield", + strictBind: "eval arguments" +}; + +exports.reservedWords = reservedWords; +// And the keywords + +var ecma5AndLessKeywords = "break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this"; + +var keywords = { + 5: ecma5AndLessKeywords, + 6: ecma5AndLessKeywords + " let const class extends export import yield super" +}; + +exports.keywords = keywords; +// ## Character categories + +// Big ugly regular expressions that match characters in the +// whitespace, identifier, and identifier-start categories. These +// are only applied when a character is found to actually have a +// code point above 128. +// Generated by `bin/generate-identifier-regex.js`. + +var nonASCIIidentifierStartChars = "ªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽͿΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԯԱ-Ֆՙա-ևא-תװ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࢠ-ࢲऄ-हऽॐक़-ॡॱ-ঀঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-హఽౘౙౠౡಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡೱೲഅ-ഌഎ-ഐഒ-ഺഽൎൠൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏼᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᛮ-ᛸᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᢰ-ᣵᤀ-ᤞᥐ-ᥭᥰ-ᥴᦀ-ᦫᧁ-ᧇᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᳩ-ᳬᳮ-ᳱᳵᳶᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕ℘-ℝℤΩℨK-ℹℼ-ℿⅅ-ⅉⅎⅠ-ↈⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞ々-〇〡-〩〱-〵〸-〼ぁ-ゖ゛-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿌ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚝꚠ-ꛯꜗ-ꜟꜢ-ꞈꞋ-ꞎꞐ-ꞭꞰꞱꟷ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꧠ-ꧤꧦ-ꧯꧺ-ꧾꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꩾ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꬰ-ꭚꭜ-ꭟꭤꭥꯀ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ"; +var nonASCIIidentifierChars = "·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-٩ٰۖ-ۜ۟-۪ۤۧۨ-ۭ۰-۹ܑܰ-݊ަ-ް߀-߉߫-߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛ࣤ-ःऺ-़ा-ॏ॑-ॗॢॣ०-९ঁ-ঃ়া-ৄেৈো-্ৗৢৣ০-৯ਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑ੦-ੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣ૦-૯ଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣ୦-୯ஂா-ூெ-ைொ-்ௗ௦-௯ఀ-ఃా-ౄె-ైొ-్ౕౖౢౣ౦-౯ಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣ೦-೯ഁ-ഃാ-ൄെ-ൈൊ-്ൗൢൣ൦-൯ංඃ්ා-ුූෘ-ෟ෦-෯ෲෳัิ-ฺ็-๎๐-๙ັິ-ູົຼ່-ໍ໐-໙༘༙༠-༩༹༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှ၀-၉ၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏ-ႝ፝-፟፩-፱ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝០-៩᠋-᠍᠐-᠙ᢩᤠ-ᤫᤰ-᤻᥆-᥏ᦰ-ᧀᧈᧉ᧐-᧚ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼-᪉᪐-᪙᪰-᪽ᬀ-ᬄ᬴-᭄᭐-᭙᭫-᭳ᮀ-ᮂᮡ-ᮭ᮰-᮹᯦-᯳ᰤ-᰷᱀-᱉᱐-᱙᳐-᳔᳒-᳨᳭ᳲ-᳴᳸᳹᷀-᷵᷼-᷿‿⁀⁔⃐-⃥⃜⃡-⃰⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꘠-꘩꙯ꙴ-꙽ꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-꣄꣐-꣙꣠-꣱꤀-꤉ꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀꧐-꧙ꧥ꧰-꧹ꨩ-ꨶꩃꩌꩍ꩐-꩙ꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭꯰-꯹ﬞ︀-️︠-︭︳︴﹍-﹏0-9_"; + +var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]"); +var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]"); + +nonASCIIidentifierStartChars = nonASCIIidentifierChars = null; + +// These are a run-length and offset encoded representation of the +// >0xffff code points that are a valid part of identifiers. The +// offset starts at 0x10000, and each pair of numbers represents an +// offset to the next range, and then a size of the range. They were +// generated by tools/generate-identifier-regex.js +var astralIdentifierStartCodes = [0, 11, 2, 25, 2, 18, 2, 1, 2, 14, 3, 13, 35, 122, 70, 52, 268, 28, 4, 48, 48, 31, 17, 26, 6, 37, 11, 29, 3, 35, 5, 7, 2, 4, 43, 157, 99, 39, 9, 51, 157, 310, 10, 21, 11, 7, 153, 5, 3, 0, 2, 43, 2, 1, 4, 0, 3, 22, 11, 22, 10, 30, 98, 21, 11, 25, 71, 55, 7, 1, 65, 0, 16, 3, 2, 2, 2, 26, 45, 28, 4, 28, 36, 7, 2, 27, 28, 53, 11, 21, 11, 18, 14, 17, 111, 72, 955, 52, 76, 44, 33, 24, 27, 35, 42, 34, 4, 0, 13, 47, 15, 3, 22, 0, 38, 17, 2, 24, 133, 46, 39, 7, 3, 1, 3, 21, 2, 6, 2, 1, 2, 4, 4, 0, 32, 4, 287, 47, 21, 1, 2, 0, 185, 46, 82, 47, 21, 0, 60, 42, 502, 63, 32, 0, 449, 56, 1288, 920, 104, 110, 2962, 1070, 13266, 568, 8, 30, 114, 29, 19, 47, 17, 3, 32, 20, 6, 18, 881, 68, 12, 0, 67, 12, 16481, 1, 3071, 106, 6, 12, 4, 8, 8, 9, 5991, 84, 2, 70, 2, 1, 3, 0, 3, 1, 3, 3, 2, 11, 2, 0, 2, 6, 2, 64, 2, 3, 3, 7, 2, 6, 2, 27, 2, 3, 2, 4, 2, 0, 4, 6, 2, 339, 3, 24, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 7, 4149, 196, 1340, 3, 2, 26, 2, 1, 2, 0, 3, 0, 2, 9, 2, 3, 2, 0, 2, 0, 7, 0, 5, 0, 2, 0, 2, 0, 2, 2, 2, 1, 2, 0, 3, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 1, 2, 0, 3, 3, 2, 6, 2, 3, 2, 3, 2, 0, 2, 9, 2, 16, 6, 2, 2, 4, 2, 16, 4421, 42710, 42, 4148, 12, 221, 16355, 541]; +var astralIdentifierCodes = [509, 0, 227, 0, 150, 4, 294, 9, 1368, 2, 2, 1, 6, 3, 41, 2, 5, 0, 166, 1, 1306, 2, 54, 14, 32, 9, 16, 3, 46, 10, 54, 9, 7, 2, 37, 13, 2, 9, 52, 0, 13, 2, 49, 13, 16, 9, 83, 11, 168, 11, 6, 9, 8, 2, 57, 0, 2, 6, 3, 1, 3, 2, 10, 0, 11, 1, 3, 6, 4, 4, 316, 19, 13, 9, 214, 6, 3, 8, 112, 16, 16, 9, 82, 12, 9, 9, 535, 9, 20855, 9, 135, 4, 60, 6, 26, 9, 1016, 45, 17, 3, 19723, 1, 5319, 4, 4, 5, 9, 7, 3, 6, 31, 3, 149, 2, 1418, 49, 4305, 6, 792618, 239]; + +// This has a complexity linear to the value of the code. The +// assumption is that looking up astral identifier characters is +// rare. +function isInAstralSet(code, set) { + var pos = 0x10000; + for (var i = 0; i < set.length; i += 2) { + pos += set[i]; + if (pos > code) return false; + pos += set[i + 1]; + if (pos >= code) return true; + } +} + +// Test whether a given character code starts an identifier. + +function isIdentifierStart(code, astral) { + if (code < 65) return code === 36; + if (code < 91) return true; + if (code < 97) return code === 95; + if (code < 123) return true; + if (code <= 0xffff) return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code)); + if (astral === false) return false; + return isInAstralSet(code, astralIdentifierStartCodes); +} + +// Test whether a given character is part of an identifier. + +function isIdentifierChar(code, astral) { + if (code < 48) return code === 36; + if (code < 58) return true; + if (code < 65) return false; + if (code < 91) return true; + if (code < 97) return code === 95; + if (code < 123) return true; + if (code <= 0xffff) return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code)); + if (astral === false) return false; + return isInAstralSet(code, astralIdentifierStartCodes) || isInAstralSet(code, astralIdentifierCodes); +} + +},{}],3:[function(_dereq_,module,exports){ // Acorn is a tiny, fast JavaScript parser written in JavaScript. // -// Acorn was written by Marijn Haverbeke and various contributors and -// released under an MIT license. The Unicode regexps (for identifiers -// and whitespace) were taken from [Esprima](http://esprima.org) by -// Ariya Hidayat. +// Acorn was written by Marijn Haverbeke, Ingvar Stepanyan, and +// various contributors and released under an MIT license. // // Git repositories for Acorn are available at // // http://marijnhaverbeke.nl/git/acorn -// https://github.com/marijnh/acorn.git +// https://github.com/ternjs/acorn.git // // Please use the [github bug tracker][ghbt] to report issues. // -// [ghbt]: https://github.com/marijnh/acorn/issues +// [ghbt]: https://github.com/ternjs/acorn/issues // // This file defines the main parser interface. The library also comes // with a [error-tolerant parser][dammit] and an // [abstract syntax tree walker][walk], defined in other files. // // [dammit]: acorn_loose.js // [walk]: util/walk.js -(function(root, mod) { - if (typeof exports == "object" && typeof module == "object") return mod(exports); // CommonJS - if (typeof define == "function" && define.amd) return define(["exports"], mod); // AMD - mod(root.acorn || (root.acorn = {})); // Plain browser env -})(this, function(exports) { - "use strict"; - - exports.version = "0.11.1"; - - // The main exported interface (under `self.acorn` when in the - // browser) is a `parse` function that takes a code string and - // returns an abstract syntax tree as specified by [Mozilla parser - // API][api], with the caveat that inline XML is not recognized. - // - // [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API - - var options, input, inputLen, sourceFile; - - exports.parse = function(inpt, opts) { - input = String(inpt); inputLen = input.length; - setOptions(opts); - initTokenState(); - var startPos = options.locations ? [tokPos, curPosition()] : tokPos; - initParserState(); - return parseTopLevel(options.program || startNodeAt(startPos)); +"use strict"; + +exports.__esModule = true; +exports.parse = parse; +exports.parseExpressionAt = parseExpressionAt; +exports.tokenizer = tokenizer; + +var _state = _dereq_("./state"); + +_dereq_("./parseutil"); + +_dereq_("./statement"); + +_dereq_("./lval"); + +_dereq_("./expression"); + +_dereq_("./location"); + +exports.Parser = _state.Parser; +exports.plugins = _state.plugins; + +var _options = _dereq_("./options"); + +exports.defaultOptions = _options.defaultOptions; + +var _locutil = _dereq_("./locutil"); + +exports.Position = _locutil.Position; +exports.SourceLocation = _locutil.SourceLocation; +exports.getLineInfo = _locutil.getLineInfo; + +var _node = _dereq_("./node"); + +exports.Node = _node.Node; + +var _tokentype = _dereq_("./tokentype"); + +exports.TokenType = _tokentype.TokenType; +exports.tokTypes = _tokentype.types; + +var _tokencontext = _dereq_("./tokencontext"); + +exports.TokContext = _tokencontext.TokContext; +exports.tokContexts = _tokencontext.types; + +var _identifier = _dereq_("./identifier"); + +exports.isIdentifierChar = _identifier.isIdentifierChar; +exports.isIdentifierStart = _identifier.isIdentifierStart; + +var _tokenize = _dereq_("./tokenize"); + +exports.Token = _tokenize.Token; + +var _whitespace = _dereq_("./whitespace"); + +exports.isNewLine = _whitespace.isNewLine; +exports.lineBreak = _whitespace.lineBreak; +exports.lineBreakG = _whitespace.lineBreakG; +var version = "2.6.4"; + +exports.version = version; +// The main exported interface (under `self.acorn` when in the +// browser) is a `parse` function that takes a code string and +// returns an abstract syntax tree as specified by [Mozilla parser +// API][api]. +// +// [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API + +function parse(input, options) { + return new _state.Parser(options, input).parse(); +} + +// This function tries to parse a single expression at a given +// offset in a string. Useful for parsing mixed-language formats +// that embed JavaScript expressions. + +function parseExpressionAt(input, pos, options) { + var p = new _state.Parser(options, input, pos); + p.nextToken(); + return p.parseExpression(); +} + +// Acorn is organized as a tokenizer and a recursive-descent parser. +// The `tokenizer` export provides an interface to the tokenizer. + +function tokenizer(input, options) { + return new _state.Parser(options, input); +} + +},{"./expression":1,"./identifier":2,"./location":4,"./locutil":5,"./lval":6,"./node":7,"./options":8,"./parseutil":9,"./state":10,"./statement":11,"./tokencontext":12,"./tokenize":13,"./tokentype":14,"./whitespace":16}],4:[function(_dereq_,module,exports){ +"use strict"; + +var _state = _dereq_("./state"); + +var _locutil = _dereq_("./locutil"); + +var pp = _state.Parser.prototype; + +// This function is used to raise exceptions on parse errors. It +// takes an offset integer (into the current `input`) to indicate +// the location of the error, attaches the position to the end +// of the error message, and then raises a `SyntaxError` with that +// message. + +pp.raise = function (pos, message) { + var loc = _locutil.getLineInfo(this.input, pos); + message += " (" + loc.line + ":" + loc.column + ")"; + var err = new SyntaxError(message); + err.pos = pos;err.loc = loc;err.raisedAt = this.pos; + throw err; +}; + +pp.curPosition = function () { + if (this.options.locations) { + return new _locutil.Position(this.curLine, this.pos - this.lineStart); + } +}; + +},{"./locutil":5,"./state":10}],5:[function(_dereq_,module,exports){ +"use strict"; + +exports.__esModule = true; +exports.getLineInfo = getLineInfo; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var _whitespace = _dereq_("./whitespace"); + +// These are used when `options.locations` is on, for the +// `startLoc` and `endLoc` properties. + +var Position = (function () { + function Position(line, col) { + _classCallCheck(this, Position); + + this.line = line; + this.column = col; + } + + Position.prototype.offset = function offset(n) { + return new Position(this.line, this.column + n); }; - // A second optional argument can be given to further configure - // the parser process. These options are recognized: - - var defaultOptions = exports.defaultOptions = { - // `ecmaVersion` indicates the ECMAScript version to parse. Must - // be either 3, or 5, or 6. This influences support for strict - // mode, the set of reserved words, support for getters and - // setters and other features. - ecmaVersion: 5, - // Turn on `strictSemicolons` to prevent the parser from doing - // automatic semicolon insertion. - strictSemicolons: false, - // When `allowTrailingCommas` is false, the parser will not allow - // trailing commas in array and object literals. - allowTrailingCommas: true, - // By default, reserved words are not enforced. Enable - // `forbidReserved` to enforce them. When this option has the - // value "everywhere", reserved words and keywords can also not be - // used as property names. - forbidReserved: false, - // When enabled, a return at the top level is not considered an - // error. - allowReturnOutsideFunction: false, - // When enabled, import/export statements are not constrained to - // appearing at the top of the program. - allowImportExportEverywhere: false, - // When enabled, hashbang directive in the beginning of file - // is allowed and treated as a line comment. - allowHashBang: false, - // When `locations` is on, `loc` properties holding objects with - // `start` and `end` properties in `{line, column}` form (with - // line being 1-based and column 0-based) will be attached to the - // nodes. - locations: false, - // A function can be passed as `onToken` option, which will - // cause Acorn to call that function with object in the same - // format as tokenize() returns. Note that you are not - // allowed to call the parser from the callback—that will - // corrupt its internal state. - onToken: null, - // A function can be passed as `onComment` option, which will - // cause Acorn to call that function with `(block, text, start, - // end)` parameters whenever a comment is skipped. `block` is a - // boolean indicating whether this is a block (`/* */`) comment, - // `text` is the content of the comment, and `start` and `end` are - // character offsets that denote the start and end of the comment. - // When the `locations` option is on, two more parameters are - // passed, the full `{line, column}` locations of the start and - // end of the comments. Note that you are not allowed to call the - // parser from the callback—that will corrupt its internal state. - onComment: null, - // Nodes have their start and end characters offsets recorded in - // `start` and `end` properties (directly on the node, rather than - // the `loc` object, which holds line/column data. To also add a - // [semi-standardized][range] `range` property holding a `[start, - // end]` array with the same numbers, set the `ranges` option to - // `true`. - // - // [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678 - ranges: false, - // It is possible to parse multiple files into a single AST by - // passing the tree produced by parsing the first file as - // `program` option in subsequent parses. This will add the - // toplevel forms of the parsed file to the `Program` (top) node - // of an existing parse tree. - program: null, - // When `locations` is on, you can pass this to record the source - // file in every node's `loc` object. - sourceFile: null, - // This value, if given, is stored in every node, whether - // `locations` is on or off. - directSourceFile: null, - // When enabled, parenthesized expressions are represented by - // (non-standard) ParenthesizedExpression nodes - preserveParens: false - }; - - // This function tries to parse a single expression at a given - // offset in a string. Useful for parsing mixed-language formats - // that embed JavaScript expressions. - - exports.parseExpressionAt = function(inpt, pos, opts) { - input = String(inpt); inputLen = input.length; - setOptions(opts); - initTokenState(pos); - initParserState(); - return parseExpression(); - }; - - var isArray = function (obj) { - return Object.prototype.toString.call(obj) === "[object Array]"; - }; - - function setOptions(opts) { - options = {}; - for (var opt in defaultOptions) - options[opt] = opts && has(opts, opt) ? opts[opt] : defaultOptions[opt]; - sourceFile = options.sourceFile || null; - if (isArray(options.onToken)) { + return Position; +})(); + +exports.Position = Position; + +var SourceLocation = function SourceLocation(p, start, end) { + _classCallCheck(this, SourceLocation); + + this.start = start; + this.end = end; + if (p.sourceFile !== null) this.source = p.sourceFile; +} + +// The `getLineInfo` function is mostly useful when the +// `locations` option is off (for performance reasons) and you +// want to find the line/column position for a given character +// offset. `input` should be the code string that the offset refers +// into. + +; + +exports.SourceLocation = SourceLocation; + +function getLineInfo(input, offset) { + for (var line = 1, cur = 0;;) { + _whitespace.lineBreakG.lastIndex = cur; + var match = _whitespace.lineBreakG.exec(input); + if (match && match.index < offset) { + ++line; + cur = match.index + match[0].length; + } else { + return new Position(line, offset - cur); + } + } +} + +},{"./whitespace":16}],6:[function(_dereq_,module,exports){ +"use strict"; + +var _tokentype = _dereq_("./tokentype"); + +var _state = _dereq_("./state"); + +var _util = _dereq_("./util"); + +var pp = _state.Parser.prototype; + +// Convert existing expression atom to assignable pattern +// if possible. + +pp.toAssignable = function (node, isBinding) { + if (this.options.ecmaVersion >= 6 && node) { + switch (node.type) { + case "Identifier": + case "ObjectPattern": + case "ArrayPattern": + break; + + case "ObjectExpression": + node.type = "ObjectPattern"; + for (var i = 0; i < node.properties.length; i++) { + var prop = node.properties[i]; + if (prop.kind !== "init") this.raise(prop.key.start, "Object pattern can't contain getter or setter"); + this.toAssignable(prop.value, isBinding); + } + break; + + case "ArrayExpression": + node.type = "ArrayPattern"; + this.toAssignableList(node.elements, isBinding); + break; + + case "AssignmentExpression": + if (node.operator === "=") { + node.type = "AssignmentPattern"; + delete node.operator; + // falls through to AssignmentPattern + } else { + this.raise(node.left.end, "Only '=' operator can be used for specifying default value."); + break; + } + + case "AssignmentPattern": + if (node.right.type === "YieldExpression") this.raise(node.right.start, "Yield expression cannot be a default value"); + break; + + case "ParenthesizedExpression": + node.expression = this.toAssignable(node.expression, isBinding); + break; + + case "MemberExpression": + if (!isBinding) break; + + default: + this.raise(node.start, "Assigning to rvalue"); + } + } + return node; +}; + +// Convert list of expression atoms to binding list. + +pp.toAssignableList = function (exprList, isBinding) { + var end = exprList.length; + if (end) { + var last = exprList[end - 1]; + if (last && last.type == "RestElement") { + --end; + } else if (last && last.type == "SpreadElement") { + last.type = "RestElement"; + var arg = last.argument; + this.toAssignable(arg, isBinding); + if (arg.type !== "Identifier" && arg.type !== "MemberExpression" && arg.type !== "ArrayPattern") this.unexpected(arg.start); + --end; + } + + if (isBinding && last.type === "RestElement" && last.argument.type !== "Identifier") this.unexpected(last.argument.start); + } + for (var i = 0; i < end; i++) { + var elt = exprList[i]; + if (elt) this.toAssignable(elt, isBinding); + } + return exprList; +}; + +// Parses spread element. + +pp.parseSpread = function (refDestructuringErrors) { + var node = this.startNode(); + this.next(); + node.argument = this.parseMaybeAssign(refDestructuringErrors); + return this.finishNode(node, "SpreadElement"); +}; + +pp.parseRest = function (allowNonIdent) { + var node = this.startNode(); + this.next(); + + // RestElement inside of a function parameter must be an identifier + if (allowNonIdent) node.argument = this.type === _tokentype.types.name ? this.parseIdent() : this.unexpected();else node.argument = this.type === _tokentype.types.name || this.type === _tokentype.types.bracketL ? this.parseBindingAtom() : this.unexpected(); + + return this.finishNode(node, "RestElement"); +}; + +// Parses lvalue (assignable) atom. + +pp.parseBindingAtom = function () { + if (this.options.ecmaVersion < 6) return this.parseIdent(); + switch (this.type) { + case _tokentype.types.name: + return this.parseIdent(); + + case _tokentype.types.bracketL: + var node = this.startNode(); + this.next(); + node.elements = this.parseBindingList(_tokentype.types.bracketR, true, true); + return this.finishNode(node, "ArrayPattern"); + + case _tokentype.types.braceL: + return this.parseObj(true); + + default: + this.unexpected(); + } +}; + +pp.parseBindingList = function (close, allowEmpty, allowTrailingComma, allowNonIdent) { + var elts = [], + first = true; + while (!this.eat(close)) { + if (first) first = false;else this.expect(_tokentype.types.comma); + if (allowEmpty && this.type === _tokentype.types.comma) { + elts.push(null); + } else if (allowTrailingComma && this.afterTrailingComma(close)) { + break; + } else if (this.type === _tokentype.types.ellipsis) { + var rest = this.parseRest(allowNonIdent); + this.parseBindingListItem(rest); + elts.push(rest); + this.expect(close); + break; + } else { + var elem = this.parseMaybeDefault(this.start, this.startLoc); + this.parseBindingListItem(elem); + elts.push(elem); + } + } + return elts; +}; + +pp.parseBindingListItem = function (param) { + return param; +}; + +// Parses assignment pattern around given atom if possible. + +pp.parseMaybeDefault = function (startPos, startLoc, left) { + left = left || this.parseBindingAtom(); + if (this.options.ecmaVersion < 6 || !this.eat(_tokentype.types.eq)) return left; + var node = this.startNodeAt(startPos, startLoc); + node.left = left; + node.right = this.parseMaybeAssign(); + return this.finishNode(node, "AssignmentPattern"); +}; + +// Verify that a node is an lval — something that can be assigned +// to. + +pp.checkLVal = function (expr, isBinding, checkClashes) { + switch (expr.type) { + case "Identifier": + if (this.strict && this.reservedWordsStrictBind.test(expr.name)) this.raise(expr.start, (isBinding ? "Binding " : "Assigning to ") + expr.name + " in strict mode"); + if (checkClashes) { + if (_util.has(checkClashes, expr.name)) this.raise(expr.start, "Argument name clash"); + checkClashes[expr.name] = true; + } + break; + + case "MemberExpression": + if (isBinding) this.raise(expr.start, (isBinding ? "Binding" : "Assigning to") + " member expression"); + break; + + case "ObjectPattern": + for (var i = 0; i < expr.properties.length; i++) { + this.checkLVal(expr.properties[i].value, isBinding, checkClashes); + }break; + + case "ArrayPattern": + for (var i = 0; i < expr.elements.length; i++) { + var elem = expr.elements[i]; + if (elem) this.checkLVal(elem, isBinding, checkClashes); + } + break; + + case "AssignmentPattern": + this.checkLVal(expr.left, isBinding, checkClashes); + break; + + case "RestElement": + this.checkLVal(expr.argument, isBinding, checkClashes); + break; + + case "ParenthesizedExpression": + this.checkLVal(expr.expression, isBinding, checkClashes); + break; + + default: + this.raise(expr.start, (isBinding ? "Binding" : "Assigning to") + " rvalue"); + } +}; + +},{"./state":10,"./tokentype":14,"./util":15}],7:[function(_dereq_,module,exports){ +"use strict"; + +exports.__esModule = true; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var _state = _dereq_("./state"); + +var _locutil = _dereq_("./locutil"); + +var Node = function Node(parser, pos, loc) { + _classCallCheck(this, Node); + + this.type = ""; + this.start = pos; + this.end = 0; + if (parser.options.locations) this.loc = new _locutil.SourceLocation(parser, loc); + if (parser.options.directSourceFile) this.sourceFile = parser.options.directSourceFile; + if (parser.options.ranges) this.range = [pos, 0]; +} + +// Start an AST node, attaching a start offset. + +; + +exports.Node = Node; +var pp = _state.Parser.prototype; + +pp.startNode = function () { + return new Node(this, this.start, this.startLoc); +}; + +pp.startNodeAt = function (pos, loc) { + return new Node(this, pos, loc); +}; + +// Finish an AST node, adding `type` and `end` properties. + +function finishNodeAt(node, type, pos, loc) { + node.type = type; + node.end = pos; + if (this.options.locations) node.loc.end = loc; + if (this.options.ranges) node.range[1] = pos; + return node; +} + +pp.finishNode = function (node, type) { + return finishNodeAt.call(this, node, type, this.lastTokEnd, this.lastTokEndLoc); +}; + +// Finish node at given position + +pp.finishNodeAt = function (node, type, pos, loc) { + return finishNodeAt.call(this, node, type, pos, loc); +}; + +},{"./locutil":5,"./state":10}],8:[function(_dereq_,module,exports){ +"use strict"; + +exports.__esModule = true; +exports.getOptions = getOptions; + +var _util = _dereq_("./util"); + +var _locutil = _dereq_("./locutil"); + +// A second optional argument can be given to further configure +// the parser process. These options are recognized: + +var defaultOptions = { + // `ecmaVersion` indicates the ECMAScript version to parse. Must + // be either 3, or 5, or 6. This influences support for strict + // mode, the set of reserved words, support for getters and + // setters and other features. + ecmaVersion: 5, + // Source type ("script" or "module") for different semantics + sourceType: "script", + // `onInsertedSemicolon` can be a callback that will be called + // when a semicolon is automatically inserted. It will be passed + // th position of the comma as an offset, and if `locations` is + // enabled, it is given the location as a `{line, column}` object + // as second argument. + onInsertedSemicolon: null, + // `onTrailingComma` is similar to `onInsertedSemicolon`, but for + // trailing commas. + onTrailingComma: null, + // By default, reserved words are only enforced if ecmaVersion >= 5. + // Set `allowReserved` to a boolean value to explicitly turn this on + // an off. When this option has the value "never", reserved words + // and keywords can also not be used as property names. + allowReserved: null, + // When enabled, a return at the top level is not considered an + // error. + allowReturnOutsideFunction: false, + // When enabled, import/export statements are not constrained to + // appearing at the top of the program. + allowImportExportEverywhere: false, + // When enabled, hashbang directive in the beginning of file + // is allowed and treated as a line comment. + allowHashBang: false, + // When `locations` is on, `loc` properties holding objects with + // `start` and `end` properties in `{line, column}` form (with + // line being 1-based and column 0-based) will be attached to the + // nodes. + locations: false, + // A function can be passed as `onToken` option, which will + // cause Acorn to call that function with object in the same + // format as tokens returned from `tokenizer().getToken()`. Note + // that you are not allowed to call the parser from the + // callback—that will corrupt its internal state. + onToken: null, + // A function can be passed as `onComment` option, which will + // cause Acorn to call that function with `(block, text, start, + // end)` parameters whenever a comment is skipped. `block` is a + // boolean indicating whether this is a block (`/* */`) comment, + // `text` is the content of the comment, and `start` and `end` are + // character offsets that denote the start and end of the comment. + // When the `locations` option is on, two more parameters are + // passed, the full `{line, column}` locations of the start and + // end of the comments. Note that you are not allowed to call the + // parser from the callback—that will corrupt its internal state. + onComment: null, + // Nodes have their start and end characters offsets recorded in + // `start` and `end` properties (directly on the node, rather than + // the `loc` object, which holds line/column data. To also add a + // [semi-standardized][range] `range` property holding a `[start, + // end]` array with the same numbers, set the `ranges` option to + // `true`. + // + // [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678 + ranges: false, + // It is possible to parse multiple files into a single AST by + // passing the tree produced by parsing the first file as + // `program` option in subsequent parses. This will add the + // toplevel forms of the parsed file to the `Program` (top) node + // of an existing parse tree. + program: null, + // When `locations` is on, you can pass this to record the source + // file in every node's `loc` object. + sourceFile: null, + // This value, if given, is stored in every node, whether + // `locations` is on or off. + directSourceFile: null, + // When enabled, parenthesized expressions are represented by + // (non-standard) ParenthesizedExpression nodes + preserveParens: false, + plugins: {} +}; + +exports.defaultOptions = defaultOptions; +// Interpret and default an options object + +function getOptions(opts) { + var options = {}; + for (var opt in defaultOptions) { + options[opt] = opts && _util.has(opts, opt) ? opts[opt] : defaultOptions[opt]; + }if (options.allowReserved == null) options.allowReserved = options.ecmaVersion < 5; + + if (_util.isArray(options.onToken)) { + (function () { var tokens = options.onToken; options.onToken = function (token) { - tokens.push(token); - }; - } - if (isArray(options.onComment)) { - var comments = options.onComment; - options.onComment = function (block, text, start, end, startLoc, endLoc) { - var comment = { - type: block ? 'Block' : 'Line', - value: text, - start: start, - end: end - }; - if (options.locations) { - comment.loc = new SourceLocation(); - comment.loc.start = startLoc; - comment.loc.end = endLoc; - } - if (options.ranges) - comment.range = [start, end]; - comments.push(comment); + return tokens.push(token); }; - } - isKeyword = options.ecmaVersion >= 6 ? isEcma6Keyword : isEcma5AndLessKeyword; + })(); + } + if (_util.isArray(options.onComment)) options.onComment = pushComment(options, options.onComment); + + return options; +} + +function pushComment(options, array) { + return function (block, text, start, end, startLoc, endLoc) { + var comment = { + type: block ? 'Block' : 'Line', + value: text, + start: start, + end: end + }; + if (options.locations) comment.loc = new _locutil.SourceLocation(this, startLoc, endLoc); + if (options.ranges) comment.range = [start, end]; + array.push(comment); + }; +} + +},{"./locutil":5,"./util":15}],9:[function(_dereq_,module,exports){ +"use strict"; + +var _tokentype = _dereq_("./tokentype"); + +var _state = _dereq_("./state"); + +var _whitespace = _dereq_("./whitespace"); + +var pp = _state.Parser.prototype; + +// ## Parser utilities + +// Test whether a statement node is the string literal `"use strict"`. + +pp.isUseStrict = function (stmt) { + return this.options.ecmaVersion >= 5 && stmt.type === "ExpressionStatement" && stmt.expression.type === "Literal" && stmt.expression.raw.slice(1, -1) === "use strict"; +}; + +// Predicate that tests whether the next token is of the given +// type, and if yes, consumes it as a side effect. + +pp.eat = function (type) { + if (this.type === type) { + this.next(); + return true; + } else { + return false; + } +}; + +// Tests whether parsed token is a contextual keyword. + +pp.isContextual = function (name) { + return this.type === _tokentype.types.name && this.value === name; +}; + +// Consumes contextual keyword if possible. + +pp.eatContextual = function (name) { + return this.value === name && this.eat(_tokentype.types.name); +}; + +// Asserts that following token is given contextual keyword. + +pp.expectContextual = function (name) { + if (!this.eatContextual(name)) this.unexpected(); +}; + +// Test whether a semicolon can be inserted at the current position. + +pp.canInsertSemicolon = function () { + return this.type === _tokentype.types.eof || this.type === _tokentype.types.braceR || _whitespace.lineBreak.test(this.input.slice(this.lastTokEnd, this.start)); +}; + +pp.insertSemicolon = function () { + if (this.canInsertSemicolon()) { + if (this.options.onInsertedSemicolon) this.options.onInsertedSemicolon(this.lastTokEnd, this.lastTokEndLoc); + return true; + } +}; + +// Consume a semicolon, or, failing that, see if we are allowed to +// pretend that there is a semicolon at this position. + +pp.semicolon = function () { + if (!this.eat(_tokentype.types.semi) && !this.insertSemicolon()) this.unexpected(); +}; + +pp.afterTrailingComma = function (tokType) { + if (this.type == tokType) { + if (this.options.onTrailingComma) this.options.onTrailingComma(this.lastTokStart, this.lastTokStartLoc); + this.next(); + return true; } - - // The `getLineInfo` function is mostly useful when the - // `locations` option is off (for performance reasons) and you - // want to find the line/column position for a given character - // offset. `input` should be the code string that the offset refers - // into. - - var getLineInfo = exports.getLineInfo = function(input, offset) { - for (var line = 1, cur = 0;;) { - lineBreak.lastIndex = cur; - var match = lineBreak.exec(input); - if (match && match.index < offset) { - ++line; - cur = match.index + match[0].length; - } else break; +}; + +// Expect a token of a given type. If found, consume it, otherwise, +// raise an unexpected token error. + +pp.expect = function (type) { + this.eat(type) || this.unexpected(); +}; + +// Raise an unexpected token error. + +pp.unexpected = function (pos) { + this.raise(pos != null ? pos : this.start, "Unexpected token"); +}; + +pp.checkPatternErrors = function (refDestructuringErrors, andThrow) { + var pos = refDestructuringErrors && refDestructuringErrors.trailingComma; + if (!andThrow) return !!pos; + if (pos) this.raise(pos, "Trailing comma is not permitted in destructuring patterns"); +}; + +pp.checkExpressionErrors = function (refDestructuringErrors, andThrow) { + var pos = refDestructuringErrors && refDestructuringErrors.shorthandAssign; + if (!andThrow) return !!pos; + if (pos) this.raise(pos, "Shorthand property assignments are valid only in destructuring patterns"); +}; + +},{"./state":10,"./tokentype":14,"./whitespace":16}],10:[function(_dereq_,module,exports){ +"use strict"; + +exports.__esModule = true; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var _identifier = _dereq_("./identifier"); + +var _tokentype = _dereq_("./tokentype"); + +var _whitespace = _dereq_("./whitespace"); + +var _options = _dereq_("./options"); + +// Registered plugins +var plugins = {}; + +exports.plugins = plugins; +function keywordRegexp(words) { + return new RegExp("^(" + words.replace(/ /g, "|") + ")$"); +} + +var Parser = (function () { + function Parser(options, input, startPos) { + _classCallCheck(this, Parser); + + this.options = options = _options.getOptions(options); + this.sourceFile = options.sourceFile; + this.keywords = keywordRegexp(_identifier.keywords[options.ecmaVersion >= 6 ? 6 : 5]); + var reserved = options.allowReserved ? "" : _identifier.reservedWords[options.ecmaVersion] + (options.sourceType == "module" ? " await" : ""); + this.reservedWords = keywordRegexp(reserved); + var reservedStrict = (reserved ? reserved + " " : "") + _identifier.reservedWords.strict; + this.reservedWordsStrict = keywordRegexp(reservedStrict); + this.reservedWordsStrictBind = keywordRegexp(reservedStrict + " " + _identifier.reservedWords.strictBind); + this.input = String(input); + + // Used to signal to callers of `readWord1` whether the word + // contained any escape sequences. This is needed because words with + // escape sequences must not be interpreted as keywords. + this.containsEsc = false; + + // Load plugins + this.loadPlugins(options.plugins); + + // Set up token state + + // The current position of the tokenizer in the input. + if (startPos) { + this.pos = startPos; + this.lineStart = Math.max(0, this.input.lastIndexOf("\n", startPos)); + this.curLine = this.input.slice(0, this.lineStart).split(_whitespace.lineBreak).length; + } else { + this.pos = this.lineStart = 0; + this.curLine = 1; } - return {line: line, column: offset - cur}; + + // Properties of the current token: + // Its type + this.type = _tokentype.types.eof; + // For tokens that include more information than their type, the value + this.value = null; + // Its start and end offset + this.start = this.end = this.pos; + // And, if locations are used, the {line, column} object + // corresponding to those offsets + this.startLoc = this.endLoc = this.curPosition(); + + // Position information for the previous token + this.lastTokEndLoc = this.lastTokStartLoc = null; + this.lastTokStart = this.lastTokEnd = this.pos; + + // The context stack is used to superficially track syntactic + // context to predict whether a regular expression is allowed in a + // given position. + this.context = this.initialContext(); + this.exprAllowed = true; + + // Figure out if it's a module code. + this.strict = this.inModule = options.sourceType === "module"; + + // Used to signify the start of a potential arrow function + this.potentialArrowAt = -1; + + // Flags to track whether we are in a function, a generator. + this.inFunction = this.inGenerator = false; + // Labels in scope. + this.labels = []; + + // If enabled, skip leading hashbang line. + if (this.pos === 0 && options.allowHashBang && this.input.slice(0, 2) === '#!') this.skipLineComment(2); + } + + // DEPRECATED Kept for backwards compatibility until 3.0 in case a plugin uses them + + Parser.prototype.isKeyword = function isKeyword(word) { + return this.keywords.test(word); + }; + + Parser.prototype.isReservedWord = function isReservedWord(word) { + return this.reservedWords.test(word); + }; + + Parser.prototype.extend = function extend(name, f) { + this[name] = f(this[name]); + }; + + Parser.prototype.loadPlugins = function loadPlugins(pluginConfigs) { + for (var _name in pluginConfigs) { + var plugin = plugins[_name]; + if (!plugin) throw new Error("Plugin '" + _name + "' not found"); + plugin(this, pluginConfigs[_name]); + } + }; + + Parser.prototype.parse = function parse() { + var node = this.options.program || this.startNode(); + this.nextToken(); + return this.parseTopLevel(node); }; - function Token() { - this.type = tokType; - this.value = tokVal; - this.start = tokStart; - this.end = tokEnd; - if (options.locations) { - this.loc = new SourceLocation(); - this.loc.end = tokEndLoc; - // TODO: remove in next major release - this.startLoc = tokStartLoc; - this.endLoc = tokEndLoc; + return Parser; +})(); + +exports.Parser = Parser; + +},{"./identifier":2,"./options":8,"./tokentype":14,"./whitespace":16}],11:[function(_dereq_,module,exports){ +"use strict"; + +var _tokentype = _dereq_("./tokentype"); + +var _state = _dereq_("./state"); + +var _whitespace = _dereq_("./whitespace"); + +var pp = _state.Parser.prototype; + +// ### Statement parsing + +// Parse a program. Initializes the parser, reads any number of +// statements, and wraps them in a Program node. Optionally takes a +// `program` argument. If present, the statements will be appended +// to its body instead of creating a new node. + +pp.parseTopLevel = function (node) { + var first = true; + if (!node.body) node.body = []; + while (this.type !== _tokentype.types.eof) { + var stmt = this.parseStatement(true, true); + node.body.push(stmt); + if (first) { + if (this.isUseStrict(stmt)) this.setStrict(true); + first = false; } - if (options.ranges) - this.range = [tokStart, tokEnd]; + } + this.next(); + if (this.options.ecmaVersion >= 6) { + node.sourceType = this.options.sourceType; + } + return this.finishNode(node, "Program"); +}; + +var loopLabel = { kind: "loop" }, + switchLabel = { kind: "switch" }; + +// Parse a single statement. +// +// If expecting a statement and finding a slash operator, parse a +// regular expression literal. This is to handle cases like +// `if (foo) /blah/.exec(foo)`, where looking at the previous token +// does not help. + +pp.parseStatement = function (declaration, topLevel) { + var starttype = this.type, + node = this.startNode(); + + // Most types of statements are recognized by the keyword they + // start with. Many are trivial to parse, some require a bit of + // complexity. + + switch (starttype) { + case _tokentype.types._break:case _tokentype.types._continue: + return this.parseBreakContinueStatement(node, starttype.keyword); + case _tokentype.types._debugger: + return this.parseDebuggerStatement(node); + case _tokentype.types._do: + return this.parseDoStatement(node); + case _tokentype.types._for: + return this.parseForStatement(node); + case _tokentype.types._function: + if (!declaration && this.options.ecmaVersion >= 6) this.unexpected(); + return this.parseFunctionStatement(node); + case _tokentype.types._class: + if (!declaration) this.unexpected(); + return this.parseClass(node, true); + case _tokentype.types._if: + return this.parseIfStatement(node); + case _tokentype.types._return: + return this.parseReturnStatement(node); + case _tokentype.types._switch: + return this.parseSwitchStatement(node); + case _tokentype.types._throw: + return this.parseThrowStatement(node); + case _tokentype.types._try: + return this.parseTryStatement(node); + case _tokentype.types._let:case _tokentype.types._const: + if (!declaration) this.unexpected(); // NOTE: falls through to _var + case _tokentype.types._var: + return this.parseVarStatement(node, starttype); + case _tokentype.types._while: + return this.parseWhileStatement(node); + case _tokentype.types._with: + return this.parseWithStatement(node); + case _tokentype.types.braceL: + return this.parseBlock(); + case _tokentype.types.semi: + return this.parseEmptyStatement(node); + case _tokentype.types._export: + case _tokentype.types._import: + if (!this.options.allowImportExportEverywhere) { + if (!topLevel) this.raise(this.start, "'import' and 'export' may only appear at the top level"); + if (!this.inModule) this.raise(this.start, "'import' and 'export' may appear only with 'sourceType: module'"); + } + return starttype === _tokentype.types._import ? this.parseImport(node) : this.parseExport(node); + + // If the statement does not start with a statement keyword or a + // brace, it's an ExpressionStatement or LabeledStatement. We + // simply start parsing an expression, and afterwards, if the + // next token is a colon and the expression was a simple + // Identifier node, we switch to interpreting it as a label. + default: + var maybeName = this.value, + expr = this.parseExpression(); + if (starttype === _tokentype.types.name && expr.type === "Identifier" && this.eat(_tokentype.types.colon)) return this.parseLabeledStatement(node, maybeName, expr);else return this.parseExpressionStatement(node, expr); + } +}; + +pp.parseBreakContinueStatement = function (node, keyword) { + var isBreak = keyword == "break"; + this.next(); + if (this.eat(_tokentype.types.semi) || this.insertSemicolon()) node.label = null;else if (this.type !== _tokentype.types.name) this.unexpected();else { + node.label = this.parseIdent(); + this.semicolon(); + } + + // Verify that there is an actual destination to break or + // continue to. + for (var i = 0; i < this.labels.length; ++i) { + var lab = this.labels[i]; + if (node.label == null || lab.name === node.label.name) { + if (lab.kind != null && (isBreak || lab.kind === "loop")) break; + if (node.label && isBreak) break; + } + } + if (i === this.labels.length) this.raise(node.start, "Unsyntactic " + keyword); + return this.finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement"); +}; + +pp.parseDebuggerStatement = function (node) { + this.next(); + this.semicolon(); + return this.finishNode(node, "DebuggerStatement"); +}; + +pp.parseDoStatement = function (node) { + this.next(); + this.labels.push(loopLabel); + node.body = this.parseStatement(false); + this.labels.pop(); + this.expect(_tokentype.types._while); + node.test = this.parseParenExpression(); + if (this.options.ecmaVersion >= 6) this.eat(_tokentype.types.semi);else this.semicolon(); + return this.finishNode(node, "DoWhileStatement"); +}; + +// Disambiguating between a `for` and a `for`/`in` or `for`/`of` +// loop is non-trivial. Basically, we have to parse the init `var` +// statement or expression, disallowing the `in` operator (see +// the second parameter to `parseExpression`), and then check +// whether the next token is `in` or `of`. When there is no init +// part (semicolon immediately after the opening parenthesis), it +// is a regular `for` loop. + +pp.parseForStatement = function (node) { + this.next(); + this.labels.push(loopLabel); + this.expect(_tokentype.types.parenL); + if (this.type === _tokentype.types.semi) return this.parseFor(node, null); + if (this.type === _tokentype.types._var || this.type === _tokentype.types._let || this.type === _tokentype.types._const) { + var _init = this.startNode(), + varKind = this.type; + this.next(); + this.parseVar(_init, true, varKind); + this.finishNode(_init, "VariableDeclaration"); + if ((this.type === _tokentype.types._in || this.options.ecmaVersion >= 6 && this.isContextual("of")) && _init.declarations.length === 1 && !(varKind !== _tokentype.types._var && _init.declarations[0].init)) return this.parseForIn(node, _init); + return this.parseFor(node, _init); + } + var refDestructuringErrors = { shorthandAssign: 0, trailingComma: 0 }; + var init = this.parseExpression(true, refDestructuringErrors); + if (this.type === _tokentype.types._in || this.options.ecmaVersion >= 6 && this.isContextual("of")) { + this.checkPatternErrors(refDestructuringErrors, true); + this.toAssignable(init); + this.checkLVal(init); + return this.parseForIn(node, init); + } else { + this.checkExpressionErrors(refDestructuringErrors, true); + } + return this.parseFor(node, init); +}; + +pp.parseFunctionStatement = function (node) { + this.next(); + return this.parseFunction(node, true); +}; + +pp.parseIfStatement = function (node) { + this.next(); + node.test = this.parseParenExpression(); + node.consequent = this.parseStatement(false); + node.alternate = this.eat(_tokentype.types._else) ? this.parseStatement(false) : null; + return this.finishNode(node, "IfStatement"); +}; + +pp.parseReturnStatement = function (node) { + if (!this.inFunction && !this.options.allowReturnOutsideFunction) this.raise(this.start, "'return' outside of function"); + this.next(); + + // In `return` (and `break`/`continue`), the keywords with + // optional arguments, we eagerly look for a semicolon or the + // possibility to insert one. + + if (this.eat(_tokentype.types.semi) || this.insertSemicolon()) node.argument = null;else { + node.argument = this.parseExpression();this.semicolon(); + } + return this.finishNode(node, "ReturnStatement"); +}; + +pp.parseSwitchStatement = function (node) { + this.next(); + node.discriminant = this.parseParenExpression(); + node.cases = []; + this.expect(_tokentype.types.braceL); + this.labels.push(switchLabel); + + // Statements under must be grouped (by label) in SwitchCase + // nodes. `cur` is used to keep the node that we are currently + // adding statements to. + + for (var cur, sawDefault = false; this.type != _tokentype.types.braceR;) { + if (this.type === _tokentype.types._case || this.type === _tokentype.types._default) { + var isCase = this.type === _tokentype.types._case; + if (cur) this.finishNode(cur, "SwitchCase"); + node.cases.push(cur = this.startNode()); + cur.consequent = []; + this.next(); + if (isCase) { + cur.test = this.parseExpression(); + } else { + if (sawDefault) this.raise(this.lastTokStart, "Multiple default clauses"); + sawDefault = true; + cur.test = null; + } + this.expect(_tokentype.types.colon); + } else { + if (!cur) this.unexpected(); + cur.consequent.push(this.parseStatement(true)); + } } - - exports.Token = Token; - - // Acorn is organized as a tokenizer and a recursive-descent parser. - // The `tokenize` export provides an interface to the tokenizer. - // Because the tokenizer is optimized for being efficiently used by - // the Acorn parser itself, this interface is somewhat crude and not - // very modular. Performing another parse or call to `tokenize` will - // reset the internal state, and invalidate existing tokenizers. - - exports.tokenize = function(inpt, opts) { - input = String(inpt); inputLen = input.length; - setOptions(opts); - initTokenState(); - skipSpace(); - - function getToken() { - lastEnd = tokEnd; - readToken(); - return new Token(); + if (cur) this.finishNode(cur, "SwitchCase"); + this.next(); // Closing brace + this.labels.pop(); + return this.finishNode(node, "SwitchStatement"); +}; + +pp.parseThrowStatement = function (node) { + this.next(); + if (_whitespace.lineBreak.test(this.input.slice(this.lastTokEnd, this.start))) this.raise(this.lastTokEnd, "Illegal newline after throw"); + node.argument = this.parseExpression(); + this.semicolon(); + return this.finishNode(node, "ThrowStatement"); +}; + +// Reused empty array added for node fields that are always empty. + +var empty = []; + +pp.parseTryStatement = function (node) { + this.next(); + node.block = this.parseBlock(); + node.handler = null; + if (this.type === _tokentype.types._catch) { + var clause = this.startNode(); + this.next(); + this.expect(_tokentype.types.parenL); + clause.param = this.parseBindingAtom(); + this.checkLVal(clause.param, true); + this.expect(_tokentype.types.parenR); + clause.body = this.parseBlock(); + node.handler = this.finishNode(clause, "CatchClause"); + } + node.finalizer = this.eat(_tokentype.types._finally) ? this.parseBlock() : null; + if (!node.handler && !node.finalizer) this.raise(node.start, "Missing catch or finally clause"); + return this.finishNode(node, "TryStatement"); +}; + +pp.parseVarStatement = function (node, kind) { + this.next(); + this.parseVar(node, false, kind); + this.semicolon(); + return this.finishNode(node, "VariableDeclaration"); +}; + +pp.parseWhileStatement = function (node) { + this.next(); + node.test = this.parseParenExpression(); + this.labels.push(loopLabel); + node.body = this.parseStatement(false); + this.labels.pop(); + return this.finishNode(node, "WhileStatement"); +}; + +pp.parseWithStatement = function (node) { + if (this.strict) this.raise(this.start, "'with' in strict mode"); + this.next(); + node.object = this.parseParenExpression(); + node.body = this.parseStatement(false); + return this.finishNode(node, "WithStatement"); +}; + +pp.parseEmptyStatement = function (node) { + this.next(); + return this.finishNode(node, "EmptyStatement"); +}; + +pp.parseLabeledStatement = function (node, maybeName, expr) { + for (var i = 0; i < this.labels.length; ++i) { + if (this.labels[i].name === maybeName) this.raise(expr.start, "Label '" + maybeName + "' is already declared"); + }var kind = this.type.isLoop ? "loop" : this.type === _tokentype.types._switch ? "switch" : null; + for (var i = this.labels.length - 1; i >= 0; i--) { + var label = this.labels[i]; + if (label.statementStart == node.start) { + label.statementStart = this.start; + label.kind = kind; + } else break; + } + this.labels.push({ name: maybeName, kind: kind, statementStart: this.start }); + node.body = this.parseStatement(true); + this.labels.pop(); + node.label = expr; + return this.finishNode(node, "LabeledStatement"); +}; + +pp.parseExpressionStatement = function (node, expr) { + node.expression = expr; + this.semicolon(); + return this.finishNode(node, "ExpressionStatement"); +}; + +// Parse a semicolon-enclosed block of statements, handling `"use +// strict"` declarations when `allowStrict` is true (used for +// function bodies). + +pp.parseBlock = function (allowStrict) { + var node = this.startNode(), + first = true, + oldStrict = undefined; + node.body = []; + this.expect(_tokentype.types.braceL); + while (!this.eat(_tokentype.types.braceR)) { + var stmt = this.parseStatement(true); + node.body.push(stmt); + if (first && allowStrict && this.isUseStrict(stmt)) { + oldStrict = this.strict; + this.setStrict(this.strict = true); } - getToken.jumpTo = function(pos, exprAllowed) { - tokPos = pos; - if (options.locations) { - tokCurLine = 1; - tokLineStart = lineBreak.lastIndex = 0; - var match; - while ((match = lineBreak.exec(input)) && match.index < pos) { - ++tokCurLine; - tokLineStart = match.index + match[0].length; + first = false; + } + if (oldStrict === false) this.setStrict(false); + return this.finishNode(node, "BlockStatement"); +}; + +// Parse a regular `for` loop. The disambiguation code in +// `parseStatement` will already have parsed the init statement or +// expression. + +pp.parseFor = function (node, init) { + node.init = init; + this.expect(_tokentype.types.semi); + node.test = this.type === _tokentype.types.semi ? null : this.parseExpression(); + this.expect(_tokentype.types.semi); + node.update = this.type === _tokentype.types.parenR ? null : this.parseExpression(); + this.expect(_tokentype.types.parenR); + node.body = this.parseStatement(false); + this.labels.pop(); + return this.finishNode(node, "ForStatement"); +}; + +// Parse a `for`/`in` and `for`/`of` loop, which are almost +// same from parser's perspective. + +pp.parseForIn = function (node, init) { + var type = this.type === _tokentype.types._in ? "ForInStatement" : "ForOfStatement"; + this.next(); + node.left = init; + node.right = this.parseExpression(); + this.expect(_tokentype.types.parenR); + node.body = this.parseStatement(false); + this.labels.pop(); + return this.finishNode(node, type); +}; + +// Parse a list of variable declarations. + +pp.parseVar = function (node, isFor, kind) { + node.declarations = []; + node.kind = kind.keyword; + for (;;) { + var decl = this.startNode(); + this.parseVarId(decl); + if (this.eat(_tokentype.types.eq)) { + decl.init = this.parseMaybeAssign(isFor); + } else if (kind === _tokentype.types._const && !(this.type === _tokentype.types._in || this.options.ecmaVersion >= 6 && this.isContextual("of"))) { + this.unexpected(); + } else if (decl.id.type != "Identifier" && !(isFor && (this.type === _tokentype.types._in || this.isContextual("of")))) { + this.raise(this.lastTokEnd, "Complex binding patterns require an initialization value"); + } else { + decl.init = null; + } + node.declarations.push(this.finishNode(decl, "VariableDeclarator")); + if (!this.eat(_tokentype.types.comma)) break; + } + return node; +}; + +pp.parseVarId = function (decl) { + decl.id = this.parseBindingAtom(); + this.checkLVal(decl.id, true); +}; + +// Parse a function declaration or literal (depending on the +// `isStatement` parameter). + +pp.parseFunction = function (node, isStatement, allowExpressionBody) { + this.initFunction(node); + if (this.options.ecmaVersion >= 6) node.generator = this.eat(_tokentype.types.star); + if (isStatement || this.type === _tokentype.types.name) node.id = this.parseIdent(); + this.parseFunctionParams(node); + this.parseFunctionBody(node, allowExpressionBody); + return this.finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression"); +}; + +pp.parseFunctionParams = function (node) { + this.expect(_tokentype.types.parenL); + node.params = this.parseBindingList(_tokentype.types.parenR, false, false, true); +}; + +// Parse a class declaration or literal (depending on the +// `isStatement` parameter). + +pp.parseClass = function (node, isStatement) { + this.next(); + this.parseClassId(node, isStatement); + this.parseClassSuper(node); + var classBody = this.startNode(); + var hadConstructor = false; + classBody.body = []; + this.expect(_tokentype.types.braceL); + while (!this.eat(_tokentype.types.braceR)) { + if (this.eat(_tokentype.types.semi)) continue; + var method = this.startNode(); + var isGenerator = this.eat(_tokentype.types.star); + var isMaybeStatic = this.type === _tokentype.types.name && this.value === "static"; + this.parsePropertyName(method); + method["static"] = isMaybeStatic && this.type !== _tokentype.types.parenL; + if (method["static"]) { + if (isGenerator) this.unexpected(); + isGenerator = this.eat(_tokentype.types.star); + this.parsePropertyName(method); + } + method.kind = "method"; + var isGetSet = false; + if (!method.computed) { + var key = method.key; + + if (!isGenerator && key.type === "Identifier" && this.type !== _tokentype.types.parenL && (key.name === "get" || key.name === "set")) { + isGetSet = true; + method.kind = key.name; + key = this.parsePropertyName(method); + } + if (!method["static"] && (key.type === "Identifier" && key.name === "constructor" || key.type === "Literal" && key.value === "constructor")) { + if (hadConstructor) this.raise(key.start, "Duplicate constructor in the same class"); + if (isGetSet) this.raise(key.start, "Constructor can't have get/set modifier"); + if (isGenerator) this.raise(key.start, "Constructor can't be a generator"); + method.kind = "constructor"; + hadConstructor = true; + } + } + this.parseClassMethod(classBody, method, isGenerator); + if (isGetSet) { + var paramCount = method.kind === "get" ? 0 : 1; + if (method.value.params.length !== paramCount) { + var start = method.value.start; + if (method.kind === "get") this.raise(start, "getter should have no params");else this.raise(start, "setter should have exactly one param"); + } + } + } + node.body = this.finishNode(classBody, "ClassBody"); + return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression"); +}; + +pp.parseClassMethod = function (classBody, method, isGenerator) { + method.value = this.parseMethod(isGenerator); + classBody.body.push(this.finishNode(method, "MethodDefinition")); +}; + +pp.parseClassId = function (node, isStatement) { + node.id = this.type === _tokentype.types.name ? this.parseIdent() : isStatement ? this.unexpected() : null; +}; + +pp.parseClassSuper = function (node) { + node.superClass = this.eat(_tokentype.types._extends) ? this.parseExprSubscripts() : null; +}; + +// Parses module export declaration. + +pp.parseExport = function (node) { + this.next(); + // export * from '...' + if (this.eat(_tokentype.types.star)) { + this.expectContextual("from"); + node.source = this.type === _tokentype.types.string ? this.parseExprAtom() : this.unexpected(); + this.semicolon(); + return this.finishNode(node, "ExportAllDeclaration"); + } + if (this.eat(_tokentype.types._default)) { + // export default ... + var expr = this.parseMaybeAssign(); + var needsSemi = true; + if (expr.type == "FunctionExpression" || expr.type == "ClassExpression") { + needsSemi = false; + if (expr.id) { + expr.type = expr.type == "FunctionExpression" ? "FunctionDeclaration" : "ClassDeclaration"; + } + } + node.declaration = expr; + if (needsSemi) this.semicolon(); + return this.finishNode(node, "ExportDefaultDeclaration"); + } + // export var|const|let|function|class ... + if (this.shouldParseExportStatement()) { + node.declaration = this.parseStatement(true); + node.specifiers = []; + node.source = null; + } else { + // export { x, y as z } [from '...'] + node.declaration = null; + node.specifiers = this.parseExportSpecifiers(); + if (this.eatContextual("from")) { + node.source = this.type === _tokentype.types.string ? this.parseExprAtom() : this.unexpected(); + } else { + // check for keywords used as local names + for (var i = 0; i < node.specifiers.length; i++) { + if (this.keywords.test(node.specifiers[i].local.name) || this.reservedWords.test(node.specifiers[i].local.name)) { + this.unexpected(node.specifiers[i].local.start); } } - tokExprAllowed = !!exprAllowed; - skipSpace(); - }; - getToken.options = options; - return getToken; - }; - - // State is kept in (closure-)global variables. We already saw the - // `options`, `input`, and `inputLen` variables above. - - // The current position of the tokenizer in the input. - - var tokPos; - - // The start and end offsets of the current token. - - var tokStart, tokEnd; - - // When `options.locations` is true, these hold objects - // containing the tokens start and end line/column pairs. - - var tokStartLoc, tokEndLoc; - - // The type and value of the current token. Token types are objects, - // named by variables against which they can be compared, and - // holding properties that describe them (indicating, for example, - // the precedence of an infix operator, and the original name of a - // keyword token). The kind of value that's held in `tokVal` depends - // on the type of the token. For literals, it is the literal value, - // for operators, the operator name, and so on. - - var tokType, tokVal; - - // Internal state for the tokenizer. To distinguish between division - // operators and regular expressions, it remembers whether the last - // token was one that is allowed to be followed by an expression. In - // some cases, notably after ')' or '}' tokens, the situation - // depends on the context before the matching opening bracket, so - // tokContext keeps a stack of information about current bracketed - // forms. - - var tokContext, tokExprAllowed; - - // When `options.locations` is true, these are used to keep - // track of the current line, and know when a new line has been - // entered. - - var tokCurLine, tokLineStart; - - // These store the position of the previous token, which is useful - // when finishing a node and assigning its `end` position. - - var lastStart, lastEnd, lastEndLoc; - - // This is the parser's state. `inFunction` is used to reject - // `return` statements outside of functions, `inGenerator` to - // reject `yield`s outside of generators, `labels` to verify - // that `break` and `continue` have somewhere to jump to, and - // `strict` indicates whether strict mode is on. - - var inFunction, inGenerator, labels, strict; - - // This counter is used for checking that arrow expressions did - // not contain nested parentheses in argument list. - - var metParenL; - - // This is used by the tokenizer to track the template strings it is - // inside, and count the amount of open braces seen inside them, to - // be able to switch back to a template token when the } to match ${ - // is encountered. It will hold an array of integers. - - var templates; - - function initParserState() { - lastStart = lastEnd = tokPos; - if (options.locations) lastEndLoc = curPosition(); - inFunction = inGenerator = strict = false; - labels = []; - skipSpace(); - readToken(); + + node.source = null; + } + this.semicolon(); + } + return this.finishNode(node, "ExportNamedDeclaration"); +}; + +pp.shouldParseExportStatement = function () { + return this.type.keyword; +}; + +// Parses a comma-separated list of module exports. + +pp.parseExportSpecifiers = function () { + var nodes = [], + first = true; + // export { x, y as z } [from '...'] + this.expect(_tokentype.types.braceL); + while (!this.eat(_tokentype.types.braceR)) { + if (!first) { + this.expect(_tokentype.types.comma); + if (this.afterTrailingComma(_tokentype.types.braceR)) break; + } else first = false; + + var node = this.startNode(); + node.local = this.parseIdent(this.type === _tokentype.types._default); + node.exported = this.eatContextual("as") ? this.parseIdent(true) : node.local; + nodes.push(this.finishNode(node, "ExportSpecifier")); + } + return nodes; +}; + +// Parses import declaration. + +pp.parseImport = function (node) { + this.next(); + // import '...' + if (this.type === _tokentype.types.string) { + node.specifiers = empty; + node.source = this.parseExprAtom(); + } else { + node.specifiers = this.parseImportSpecifiers(); + this.expectContextual("from"); + node.source = this.type === _tokentype.types.string ? this.parseExprAtom() : this.unexpected(); + } + this.semicolon(); + return this.finishNode(node, "ImportDeclaration"); +}; + +// Parses a comma-separated list of module imports. + +pp.parseImportSpecifiers = function () { + var nodes = [], + first = true; + if (this.type === _tokentype.types.name) { + // import defaultObj, { x, y as z } from '...' + var node = this.startNode(); + node.local = this.parseIdent(); + this.checkLVal(node.local, true); + nodes.push(this.finishNode(node, "ImportDefaultSpecifier")); + if (!this.eat(_tokentype.types.comma)) return nodes; + } + if (this.type === _tokentype.types.star) { + var node = this.startNode(); + this.next(); + this.expectContextual("as"); + node.local = this.parseIdent(); + this.checkLVal(node.local, true); + nodes.push(this.finishNode(node, "ImportNamespaceSpecifier")); + return nodes; + } + this.expect(_tokentype.types.braceL); + while (!this.eat(_tokentype.types.braceR)) { + if (!first) { + this.expect(_tokentype.types.comma); + if (this.afterTrailingComma(_tokentype.types.braceR)) break; + } else first = false; + + var node = this.startNode(); + node.imported = this.parseIdent(true); + node.local = this.eatContextual("as") ? this.parseIdent() : node.imported; + this.checkLVal(node.local, true); + nodes.push(this.finishNode(node, "ImportSpecifier")); + } + return nodes; +}; + +},{"./state":10,"./tokentype":14,"./whitespace":16}],12:[function(_dereq_,module,exports){ +// The algorithm used to determine whether a regexp can appear at a +// given point in the program is loosely based on sweet.js' approach. +// See https://github.com/mozilla/sweet.js/wiki/design + +"use strict"; + +exports.__esModule = true; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var _state = _dereq_("./state"); + +var _tokentype = _dereq_("./tokentype"); + +var _whitespace = _dereq_("./whitespace"); + +var TokContext = function TokContext(token, isExpr, preserveSpace, override) { + _classCallCheck(this, TokContext); + + this.token = token; + this.isExpr = !!isExpr; + this.preserveSpace = !!preserveSpace; + this.override = override; +}; + +exports.TokContext = TokContext; +var types = { + b_stat: new TokContext("{", false), + b_expr: new TokContext("{", true), + b_tmpl: new TokContext("${", true), + p_stat: new TokContext("(", false), + p_expr: new TokContext("(", true), + q_tmpl: new TokContext("`", true, true, function (p) { + return p.readTmplToken(); + }), + f_expr: new TokContext("function", true) +}; + +exports.types = types; +var pp = _state.Parser.prototype; + +pp.initialContext = function () { + return [types.b_stat]; +}; + +pp.braceIsBlock = function (prevType) { + if (prevType === _tokentype.types.colon) { + var _parent = this.curContext(); + if (_parent === types.b_stat || _parent === types.b_expr) return !_parent.isExpr; + } + if (prevType === _tokentype.types._return) return _whitespace.lineBreak.test(this.input.slice(this.lastTokEnd, this.start)); + if (prevType === _tokentype.types._else || prevType === _tokentype.types.semi || prevType === _tokentype.types.eof || prevType === _tokentype.types.parenR) return true; + if (prevType == _tokentype.types.braceL) return this.curContext() === types.b_stat; + return !this.exprAllowed; +}; + +pp.updateContext = function (prevType) { + var update = undefined, + type = this.type; + if (type.keyword && prevType == _tokentype.types.dot) this.exprAllowed = false;else if (update = type.updateContext) update.call(this, prevType);else this.exprAllowed = type.beforeExpr; +}; + +// Token-specific context update code + +_tokentype.types.parenR.updateContext = _tokentype.types.braceR.updateContext = function () { + if (this.context.length == 1) { + this.exprAllowed = true; + return; + } + var out = this.context.pop(); + if (out === types.b_stat && this.curContext() === types.f_expr) { + this.context.pop(); + this.exprAllowed = false; + } else if (out === types.b_tmpl) { + this.exprAllowed = true; + } else { + this.exprAllowed = !out.isExpr; + } +}; + +_tokentype.types.braceL.updateContext = function (prevType) { + this.context.push(this.braceIsBlock(prevType) ? types.b_stat : types.b_expr); + this.exprAllowed = true; +}; + +_tokentype.types.dollarBraceL.updateContext = function () { + this.context.push(types.b_tmpl); + this.exprAllowed = true; +}; + +_tokentype.types.parenL.updateContext = function (prevType) { + var statementParens = prevType === _tokentype.types._if || prevType === _tokentype.types._for || prevType === _tokentype.types._with || prevType === _tokentype.types._while; + this.context.push(statementParens ? types.p_stat : types.p_expr); + this.exprAllowed = true; +}; + +_tokentype.types.incDec.updateContext = function () { + // tokExprAllowed stays unchanged +}; + +_tokentype.types._function.updateContext = function () { + if (this.curContext() !== types.b_stat) this.context.push(types.f_expr); + this.exprAllowed = false; +}; + +_tokentype.types.backQuote.updateContext = function () { + if (this.curContext() === types.q_tmpl) this.context.pop();else this.context.push(types.q_tmpl); + this.exprAllowed = false; +}; + +},{"./state":10,"./tokentype":14,"./whitespace":16}],13:[function(_dereq_,module,exports){ +"use strict"; + +exports.__esModule = true; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var _identifier = _dereq_("./identifier"); + +var _tokentype = _dereq_("./tokentype"); + +var _state = _dereq_("./state"); + +var _locutil = _dereq_("./locutil"); + +var _whitespace = _dereq_("./whitespace"); + +// Object type used to represent tokens. Note that normally, tokens +// simply exist as properties on the parser object. This is only +// used for the onToken callback and the external tokenizer. + +var Token = function Token(p) { + _classCallCheck(this, Token); + + this.type = p.type; + this.value = p.value; + this.start = p.start; + this.end = p.end; + if (p.options.locations) this.loc = new _locutil.SourceLocation(p, p.startLoc, p.endLoc); + if (p.options.ranges) this.range = [p.start, p.end]; +} + +// ## Tokenizer + +; + +exports.Token = Token; +var pp = _state.Parser.prototype; + +// Are we running under Rhino? +var isRhino = typeof Packages == "object" && Object.prototype.toString.call(Packages) == "[object JavaPackage]"; + +// Move to the next token + +pp.next = function () { + if (this.options.onToken) this.options.onToken(new Token(this)); + + this.lastTokEnd = this.end; + this.lastTokStart = this.start; + this.lastTokEndLoc = this.endLoc; + this.lastTokStartLoc = this.startLoc; + this.nextToken(); +}; + +pp.getToken = function () { + this.next(); + return new Token(this); +}; + +// If we're in an ES6 environment, make parsers iterable +if (typeof Symbol !== "undefined") pp[Symbol.iterator] = function () { + var self = this; + return { next: function next() { + var token = self.getToken(); + return { + done: token.type === _tokentype.types.eof, + value: token + }; + } }; +}; + +// Toggle strict mode. Re-reads the next number or string to please +// pedantic tests (`"use strict"; 010;` should fail). + +pp.setStrict = function (strict) { + this.strict = strict; + if (this.type !== _tokentype.types.num && this.type !== _tokentype.types.string) return; + this.pos = this.start; + if (this.options.locations) { + while (this.pos < this.lineStart) { + this.lineStart = this.input.lastIndexOf("\n", this.lineStart - 2) + 1; + --this.curLine; + } + } + this.nextToken(); +}; + +pp.curContext = function () { + return this.context[this.context.length - 1]; +}; + +// Read a single token, updating the parser object's token-related +// properties. + +pp.nextToken = function () { + var curContext = this.curContext(); + if (!curContext || !curContext.preserveSpace) this.skipSpace(); + + this.start = this.pos; + if (this.options.locations) this.startLoc = this.curPosition(); + if (this.pos >= this.input.length) return this.finishToken(_tokentype.types.eof); + + if (curContext.override) return curContext.override(this);else this.readToken(this.fullCharCodeAtPos()); +}; + +pp.readToken = function (code) { + // Identifier or keyword. '\uXXXX' sequences are allowed in + // identifiers, so '\' also dispatches to that. + if (_identifier.isIdentifierStart(code, this.options.ecmaVersion >= 6) || code === 92 /* '\' */) return this.readWord(); + + return this.getTokenFromCode(code); +}; + +pp.fullCharCodeAtPos = function () { + var code = this.input.charCodeAt(this.pos); + if (code <= 0xd7ff || code >= 0xe000) return code; + var next = this.input.charCodeAt(this.pos + 1); + return (code << 10) + next - 0x35fdc00; +}; + +pp.skipBlockComment = function () { + var startLoc = this.options.onComment && this.curPosition(); + var start = this.pos, + end = this.input.indexOf("*/", this.pos += 2); + if (end === -1) this.raise(this.pos - 2, "Unterminated comment"); + this.pos = end + 2; + if (this.options.locations) { + _whitespace.lineBreakG.lastIndex = start; + var match = undefined; + while ((match = _whitespace.lineBreakG.exec(this.input)) && match.index < this.pos) { + ++this.curLine; + this.lineStart = match.index + match[0].length; + } + } + if (this.options.onComment) this.options.onComment(true, this.input.slice(start + 2, end), start, this.pos, startLoc, this.curPosition()); +}; + +pp.skipLineComment = function (startSkip) { + var start = this.pos; + var startLoc = this.options.onComment && this.curPosition(); + var ch = this.input.charCodeAt(this.pos += startSkip); + while (this.pos < this.input.length && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) { + ++this.pos; + ch = this.input.charCodeAt(this.pos); + } + if (this.options.onComment) this.options.onComment(false, this.input.slice(start + startSkip, this.pos), start, this.pos, startLoc, this.curPosition()); +}; + +// Called at the start of the parse and after every token. Skips +// whitespace and comments, and. + +pp.skipSpace = function () { + loop: while (this.pos < this.input.length) { + var ch = this.input.charCodeAt(this.pos); + switch (ch) { + case 32:case 160: + // ' ' + ++this.pos; + break; + case 13: + if (this.input.charCodeAt(this.pos + 1) === 10) { + ++this.pos; + } + case 10:case 8232:case 8233: + ++this.pos; + if (this.options.locations) { + ++this.curLine; + this.lineStart = this.pos; + } + break; + case 47: + // '/' + switch (this.input.charCodeAt(this.pos + 1)) { + case 42: + // '*' + this.skipBlockComment(); + break; + case 47: + this.skipLineComment(2); + break; + default: + break loop; + } + break; + default: + if (ch > 8 && ch < 14 || ch >= 5760 && _whitespace.nonASCIIwhitespace.test(String.fromCharCode(ch))) { + ++this.pos; + } else { + break loop; + } + } + } +}; + +// Called at the end of every token. Sets `end`, `val`, and +// maintains `context` and `exprAllowed`, and skips the space after +// the token, so that the next one's `start` will point at the +// right position. + +pp.finishToken = function (type, val) { + this.end = this.pos; + if (this.options.locations) this.endLoc = this.curPosition(); + var prevType = this.type; + this.type = type; + this.value = val; + + this.updateContext(prevType); +}; + +// ### Token reading + +// This is the function that is called to fetch the next token. It +// is somewhat obscure, because it works in character codes rather +// than characters, and because operator parsing has been inlined +// into it. +// +// All in the name of speed. +// +pp.readToken_dot = function () { + var next = this.input.charCodeAt(this.pos + 1); + if (next >= 48 && next <= 57) return this.readNumber(true); + var next2 = this.input.charCodeAt(this.pos + 2); + if (this.options.ecmaVersion >= 6 && next === 46 && next2 === 46) { + // 46 = dot '.' + this.pos += 3; + return this.finishToken(_tokentype.types.ellipsis); + } else { + ++this.pos; + return this.finishToken(_tokentype.types.dot); + } +}; + +pp.readToken_slash = function () { + // '/' + var next = this.input.charCodeAt(this.pos + 1); + if (this.exprAllowed) { + ++this.pos;return this.readRegexp(); + } + if (next === 61) return this.finishOp(_tokentype.types.assign, 2); + return this.finishOp(_tokentype.types.slash, 1); +}; + +pp.readToken_mult_modulo = function (code) { + // '%*' + var next = this.input.charCodeAt(this.pos + 1); + if (next === 61) return this.finishOp(_tokentype.types.assign, 2); + return this.finishOp(code === 42 ? _tokentype.types.star : _tokentype.types.modulo, 1); +}; + +pp.readToken_pipe_amp = function (code) { + // '|&' + var next = this.input.charCodeAt(this.pos + 1); + if (next === code) return this.finishOp(code === 124 ? _tokentype.types.logicalOR : _tokentype.types.logicalAND, 2); + if (next === 61) return this.finishOp(_tokentype.types.assign, 2); + return this.finishOp(code === 124 ? _tokentype.types.bitwiseOR : _tokentype.types.bitwiseAND, 1); +}; + +pp.readToken_caret = function () { + // '^' + var next = this.input.charCodeAt(this.pos + 1); + if (next === 61) return this.finishOp(_tokentype.types.assign, 2); + return this.finishOp(_tokentype.types.bitwiseXOR, 1); +}; + +pp.readToken_plus_min = function (code) { + // '+-' + var next = this.input.charCodeAt(this.pos + 1); + if (next === code) { + if (next == 45 && this.input.charCodeAt(this.pos + 2) == 62 && _whitespace.lineBreak.test(this.input.slice(this.lastTokEnd, this.pos))) { + // A `-->` line comment + this.skipLineComment(3); + this.skipSpace(); + return this.nextToken(); + } + return this.finishOp(_tokentype.types.incDec, 2); + } + if (next === 61) return this.finishOp(_tokentype.types.assign, 2); + return this.finishOp(_tokentype.types.plusMin, 1); +}; + +pp.readToken_lt_gt = function (code) { + // '<>' + var next = this.input.charCodeAt(this.pos + 1); + var size = 1; + if (next === code) { + size = code === 62 && this.input.charCodeAt(this.pos + 2) === 62 ? 3 : 2; + if (this.input.charCodeAt(this.pos + size) === 61) return this.finishOp(_tokentype.types.assign, size + 1); + return this.finishOp(_tokentype.types.bitShift, size); + } + if (next == 33 && code == 60 && this.input.charCodeAt(this.pos + 2) == 45 && this.input.charCodeAt(this.pos + 3) == 45) { + if (this.inModule) this.unexpected(); + // `<!--`, an XML-style comment that should be interpreted as a line comment + this.skipLineComment(4); + this.skipSpace(); + return this.nextToken(); + } + if (next === 61) size = this.input.charCodeAt(this.pos + 2) === 61 ? 3 : 2; + return this.finishOp(_tokentype.types.relational, size); +}; + +pp.readToken_eq_excl = function (code) { + // '=!' + var next = this.input.charCodeAt(this.pos + 1); + if (next === 61) return this.finishOp(_tokentype.types.equality, this.input.charCodeAt(this.pos + 2) === 61 ? 3 : 2); + if (code === 61 && next === 62 && this.options.ecmaVersion >= 6) { + // '=>' + this.pos += 2; + return this.finishToken(_tokentype.types.arrow); } - - // This function is used to raise exceptions on parse errors. It - // takes an offset integer (into the current `input`) to indicate - // the location of the error, attaches the position to the end - // of the error message, and then raises a `SyntaxError` with that - // message. - - function raise(pos, message) { - var loc = getLineInfo(input, pos); - message += " (" + loc.line + ":" + loc.column + ")"; - var err = new SyntaxError(message); - err.pos = pos; err.loc = loc; err.raisedAt = tokPos; - throw err; + return this.finishOp(code === 61 ? _tokentype.types.eq : _tokentype.types.prefix, 1); +}; + +pp.getTokenFromCode = function (code) { + switch (code) { + // The interpretation of a dot depends on whether it is followed + // by a digit or another two dots. + case 46: + // '.' + return this.readToken_dot(); + + // Punctuation tokens. + case 40: + ++this.pos;return this.finishToken(_tokentype.types.parenL); + case 41: + ++this.pos;return this.finishToken(_tokentype.types.parenR); + case 59: + ++this.pos;return this.finishToken(_tokentype.types.semi); + case 44: + ++this.pos;return this.finishToken(_tokentype.types.comma); + case 91: + ++this.pos;return this.finishToken(_tokentype.types.bracketL); + case 93: + ++this.pos;return this.finishToken(_tokentype.types.bracketR); + case 123: + ++this.pos;return this.finishToken(_tokentype.types.braceL); + case 125: + ++this.pos;return this.finishToken(_tokentype.types.braceR); + case 58: + ++this.pos;return this.finishToken(_tokentype.types.colon); + case 63: + ++this.pos;return this.finishToken(_tokentype.types.question); + + case 96: + // '`' + if (this.options.ecmaVersion < 6) break; + ++this.pos; + return this.finishToken(_tokentype.types.backQuote); + + case 48: + // '0' + var next = this.input.charCodeAt(this.pos + 1); + if (next === 120 || next === 88) return this.readRadixNumber(16); // '0x', '0X' - hex number + if (this.options.ecmaVersion >= 6) { + if (next === 111 || next === 79) return this.readRadixNumber(8); // '0o', '0O' - octal number + if (next === 98 || next === 66) return this.readRadixNumber(2); // '0b', '0B' - binary number + } + // Anything else beginning with a digit is an integer, octal + // number, or float. + case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57: + // 1-9 + return this.readNumber(false); + + // Quotes produce strings. + case 34:case 39: + // '"', "'" + return this.readString(code); + + // Operators are parsed inline in tiny state machines. '=' (61) is + // often referred to. `finishOp` simply skips the amount of + // characters it is given as second argument, and returns a token + // of the type given by its first argument. + + case 47: + // '/' + return this.readToken_slash(); + + case 37:case 42: + // '%*' + return this.readToken_mult_modulo(code); + + case 124:case 38: + // '|&' + return this.readToken_pipe_amp(code); + + case 94: + // '^' + return this.readToken_caret(); + + case 43:case 45: + // '+-' + return this.readToken_plus_min(code); + + case 60:case 62: + // '<>' + return this.readToken_lt_gt(code); + + case 61:case 33: + // '=!' + return this.readToken_eq_excl(code); + + case 126: + // '~' + return this.finishOp(_tokentype.types.prefix, 1); + } + + this.raise(this.pos, "Unexpected character '" + codePointToString(code) + "'"); +}; + +pp.finishOp = function (type, size) { + var str = this.input.slice(this.pos, this.pos + size); + this.pos += size; + return this.finishToken(type, str); +}; + +// Parse a regular expression. Some context-awareness is necessary, +// since a '/' inside a '[]' set does not end the expression. + +function tryCreateRegexp(src, flags, throwErrorAt, parser) { + try { + return new RegExp(src, flags); + } catch (e) { + if (throwErrorAt !== undefined) { + if (e instanceof SyntaxError) parser.raise(throwErrorAt, "Error parsing regular expression: " + e.message); + throw e; + } + } +} + +var regexpUnicodeSupport = !!tryCreateRegexp("", "u"); + +pp.readRegexp = function () { + var _this = this; + + var escaped = undefined, + inClass = undefined, + start = this.pos; + for (;;) { + if (this.pos >= this.input.length) this.raise(start, "Unterminated regular expression"); + var ch = this.input.charAt(this.pos); + if (_whitespace.lineBreak.test(ch)) this.raise(start, "Unterminated regular expression"); + if (!escaped) { + if (ch === "[") inClass = true;else if (ch === "]" && inClass) inClass = false;else if (ch === "/" && !inClass) break; + escaped = ch === "\\"; + } else escaped = false; + ++this.pos; + } + var content = this.input.slice(start, this.pos); + ++this.pos; + // Need to use `readWord1` because '\uXXXX' sequences are allowed + // here (don't ask). + var mods = this.readWord1(); + var tmp = content; + if (mods) { + var validFlags = /^[gmsiy]*$/; + if (this.options.ecmaVersion >= 6) validFlags = /^[gmsiyu]*$/; + if (!validFlags.test(mods)) this.raise(start, "Invalid regular expression flag"); + if (mods.indexOf('u') >= 0 && !regexpUnicodeSupport) { + // Replace each astral symbol and every Unicode escape sequence that + // possibly represents an astral symbol or a paired surrogate with a + // single ASCII symbol to avoid throwing on regular expressions that + // are only valid in combination with the `/u` flag. + // Note: replacing with the ASCII symbol `x` might cause false + // negatives in unlikely scenarios. For example, `[\u{61}-b]` is a + // perfectly valid pattern that is equivalent to `[a-b]`, but it would + // be replaced by `[x-b]` which throws an error. + tmp = tmp.replace(/\\u\{([0-9a-fA-F]+)\}/g, function (_match, code, offset) { + code = Number("0x" + code); + if (code > 0x10FFFF) _this.raise(start + offset + 3, "Code point out of bounds"); + return "x"; + }); + tmp = tmp.replace(/\\u([a-fA-F0-9]{4})|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "x"); + } + } + // Detect invalid regular expressions. + var value = null; + // Rhino's regular expression parser is flaky and throws uncatchable exceptions, + // so don't do detection if we are running under Rhino + if (!isRhino) { + tryCreateRegexp(tmp, undefined, start, this); + // Get a regular expression object for this pattern-flag pair, or `null` in + // case the current environment doesn't support the flags it uses. + value = tryCreateRegexp(content, mods); + } + return this.finishToken(_tokentype.types.regexp, { pattern: content, flags: mods, value: value }); +}; + +// Read an integer in the given radix. Return null if zero digits +// were read, the integer value otherwise. When `len` is given, this +// will return `null` unless the integer has exactly `len` digits. + +pp.readInt = function (radix, len) { + var start = this.pos, + total = 0; + for (var i = 0, e = len == null ? Infinity : len; i < e; ++i) { + var code = this.input.charCodeAt(this.pos), + val = undefined; + if (code >= 97) val = code - 97 + 10; // a + else if (code >= 65) val = code - 65 + 10; // A + else if (code >= 48 && code <= 57) val = code - 48; // 0-9 + else val = Infinity; + if (val >= radix) break; + ++this.pos; + total = total * radix + val; + } + if (this.pos === start || len != null && this.pos - start !== len) return null; + + return total; +}; + +pp.readRadixNumber = function (radix) { + this.pos += 2; // 0x + var val = this.readInt(radix); + if (val == null) this.raise(this.start + 2, "Expected number in radix " + radix); + if (_identifier.isIdentifierStart(this.fullCharCodeAtPos())) this.raise(this.pos, "Identifier directly after number"); + return this.finishToken(_tokentype.types.num, val); +}; + +// Read an integer, octal integer, or floating-point number. + +pp.readNumber = function (startsWithDot) { + var start = this.pos, + isFloat = false, + octal = this.input.charCodeAt(this.pos) === 48; + if (!startsWithDot && this.readInt(10) === null) this.raise(start, "Invalid number"); + var next = this.input.charCodeAt(this.pos); + if (next === 46) { + // '.' + ++this.pos; + this.readInt(10); + isFloat = true; + next = this.input.charCodeAt(this.pos); + } + if (next === 69 || next === 101) { + // 'eE' + next = this.input.charCodeAt(++this.pos); + if (next === 43 || next === 45) ++this.pos; // '+-' + if (this.readInt(10) === null) this.raise(start, "Invalid number"); + isFloat = true; + } + if (_identifier.isIdentifierStart(this.fullCharCodeAtPos())) this.raise(this.pos, "Identifier directly after number"); + + var str = this.input.slice(start, this.pos), + val = undefined; + if (isFloat) val = parseFloat(str);else if (!octal || str.length === 1) val = parseInt(str, 10);else if (/[89]/.test(str) || this.strict) this.raise(start, "Invalid number");else val = parseInt(str, 8); + return this.finishToken(_tokentype.types.num, val); +}; + +// Read a string value, interpreting backslash-escapes. + +pp.readCodePoint = function () { + var ch = this.input.charCodeAt(this.pos), + code = undefined; + + if (ch === 123) { + if (this.options.ecmaVersion < 6) this.unexpected(); + var codePos = ++this.pos; + code = this.readHexChar(this.input.indexOf('}', this.pos) - this.pos); + ++this.pos; + if (code > 0x10FFFF) this.raise(codePos, "Code point out of bounds"); + } else { + code = this.readHexChar(4); } - - // Reused empty array added for node fields that are always empty. - - var empty = []; - - // ## Token types - - // The assignment of fine-grained, information-carrying type objects - // allows the tokenizer to store the information it has about a - // token in a way that is very cheap for the parser to look up. - - // All token type variables start with an underscore, to make them - // easy to recognize. - - // These are the general types. The `type` property is only used to - // make them recognizeable when debugging. - - var _num = {type: "num"}, _regexp = {type: "regexp"}, _string = {type: "string"}; - var _name = {type: "name"}, _eof = {type: "eof"}; - - // Keyword tokens. The `keyword` property (also used in keyword-like - // operators) indicates that the token originated from an - // identifier-like word, which is used when parsing property names. - // - // The `beforeExpr` property is used to disambiguate between regular - // expressions and divisions. It is set on all token types that can - // be followed by an expression (thus, a slash after them would be a - // regular expression). - // - // `isLoop` marks a keyword as starting a loop, which is important - // to know when parsing a label, in order to allow or disallow - // continue jumps to that label. - - var _break = {keyword: "break"}, _case = {keyword: "case", beforeExpr: true}, _catch = {keyword: "catch"}; - var _continue = {keyword: "continue"}, _debugger = {keyword: "debugger"}, _default = {keyword: "default"}; - var _do = {keyword: "do", isLoop: true}, _else = {keyword: "else", beforeExpr: true}; - var _finally = {keyword: "finally"}, _for = {keyword: "for", isLoop: true}, _function = {keyword: "function"}; - var _if = {keyword: "if"}, _return = {keyword: "return", beforeExpr: true}, _switch = {keyword: "switch"}; - var _throw = {keyword: "throw", beforeExpr: true}, _try = {keyword: "try"}, _var = {keyword: "var"}; - var _let = {keyword: "let"}, _const = {keyword: "const"}; - var _while = {keyword: "while", isLoop: true}, _with = {keyword: "with"}, _new = {keyword: "new", beforeExpr: true}; - var _this = {keyword: "this"}; - var _class = {keyword: "class"}, _extends = {keyword: "extends", beforeExpr: true}; - var _export = {keyword: "export"}, _import = {keyword: "import"}; - var _yield = {keyword: "yield", beforeExpr: true}; - - // The keywords that denote values. - - var _null = {keyword: "null", atomValue: null}, _true = {keyword: "true", atomValue: true}; - var _false = {keyword: "false", atomValue: false}; - - // Some keywords are treated as regular operators. `in` sometimes - // (when parsing `for`) needs to be tested against specifically, so - // we assign a variable name to it for quick comparing. - - var _in = {keyword: "in", binop: 7, beforeExpr: true}; - - // Map keyword names to token types. - - var keywordTypes = {"break": _break, "case": _case, "catch": _catch, - "continue": _continue, "debugger": _debugger, "default": _default, - "do": _do, "else": _else, "finally": _finally, "for": _for, - "function": _function, "if": _if, "return": _return, "switch": _switch, - "throw": _throw, "try": _try, "var": _var, "let": _let, "const": _const, - "while": _while, "with": _with, - "null": _null, "true": _true, "false": _false, "new": _new, "in": _in, - "instanceof": {keyword: "instanceof", binop: 7, beforeExpr: true}, "this": _this, - "typeof": {keyword: "typeof", prefix: true, beforeExpr: true}, - "void": {keyword: "void", prefix: true, beforeExpr: true}, - "delete": {keyword: "delete", prefix: true, beforeExpr: true}, - "class": _class, "extends": _extends, - "export": _export, "import": _import, "yield": _yield}; - - // Punctuation token types. Again, the `type` property is purely for debugging. - - var _bracketL = {type: "[", beforeExpr: true}, _bracketR = {type: "]"}, _braceL = {type: "{", beforeExpr: true}; - var _braceR = {type: "}"}, _parenL = {type: "(", beforeExpr: true}, _parenR = {type: ")"}; - var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true}; - var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _question = {type: "?", beforeExpr: true}; - var _arrow = {type: "=>", beforeExpr: true}, _template = {type: "template"}, _templateContinued = {type: "templateContinued"}; - var _ellipsis = {type: "...", prefix: true, beforeExpr: true}; + return code; +}; + +function codePointToString(code) { + // UTF-16 Decoding + if (code <= 0xFFFF) return String.fromCharCode(code); + code -= 0x10000; + return String.fromCharCode((code >> 10) + 0xD800, (code & 1023) + 0xDC00); +} + +pp.readString = function (quote) { + var out = "", + chunkStart = ++this.pos; + for (;;) { + if (this.pos >= this.input.length) this.raise(this.start, "Unterminated string constant"); + var ch = this.input.charCodeAt(this.pos); + if (ch === quote) break; + if (ch === 92) { + // '\' + out += this.input.slice(chunkStart, this.pos); + out += this.readEscapedChar(false); + chunkStart = this.pos; + } else { + if (_whitespace.isNewLine(ch)) this.raise(this.start, "Unterminated string constant"); + ++this.pos; + } + } + out += this.input.slice(chunkStart, this.pos++); + return this.finishToken(_tokentype.types.string, out); +}; + +// Reads template string tokens. + +pp.readTmplToken = function () { + var out = "", + chunkStart = this.pos; + for (;;) { + if (this.pos >= this.input.length) this.raise(this.start, "Unterminated template"); + var ch = this.input.charCodeAt(this.pos); + if (ch === 96 || ch === 36 && this.input.charCodeAt(this.pos + 1) === 123) { + // '`', '${' + if (this.pos === this.start && this.type === _tokentype.types.template) { + if (ch === 36) { + this.pos += 2; + return this.finishToken(_tokentype.types.dollarBraceL); + } else { + ++this.pos; + return this.finishToken(_tokentype.types.backQuote); + } + } + out += this.input.slice(chunkStart, this.pos); + return this.finishToken(_tokentype.types.template, out); + } + if (ch === 92) { + // '\' + out += this.input.slice(chunkStart, this.pos); + out += this.readEscapedChar(true); + chunkStart = this.pos; + } else if (_whitespace.isNewLine(ch)) { + out += this.input.slice(chunkStart, this.pos); + ++this.pos; + switch (ch) { + case 13: + if (this.input.charCodeAt(this.pos) === 10) ++this.pos; + case 10: + out += "\n"; + break; + default: + out += String.fromCharCode(ch); + break; + } + if (this.options.locations) { + ++this.curLine; + this.lineStart = this.pos; + } + chunkStart = this.pos; + } else { + ++this.pos; + } + } +}; + +// Used to read escaped characters + +pp.readEscapedChar = function (inTemplate) { + var ch = this.input.charCodeAt(++this.pos); + ++this.pos; + switch (ch) { + case 110: + return "\n"; // 'n' -> '\n' + case 114: + return "\r"; // 'r' -> '\r' + case 120: + return String.fromCharCode(this.readHexChar(2)); // 'x' + case 117: + return codePointToString(this.readCodePoint()); // 'u' + case 116: + return "\t"; // 't' -> '\t' + case 98: + return "\b"; // 'b' -> '\b' + case 118: + return "\u000b"; // 'v' -> '\u000b' + case 102: + return "\f"; // 'f' -> '\f'