From 3bf1d0925abb7ca7b34ed2fa04c108d268779caf Mon Sep 17 00:00:00 2001 From: Sebastian Benz Date: Tue, 28 Jan 2020 12:43:59 +0100 Subject: [PATCH] Derek non generic feat/adopt prettier (#3426) * Install & implement prettier * Run `npm run fix:node` * Fix remaining errors * fixed lint errors Co-authored-by: Derek Lewis --- .eslintrc.json | 13 +- .prettierrc | 20 + boilerplate/backend/index.js | 36 +- boilerplate/build.js | 7 +- boilerplate/lib/io.js | 2 +- boilerplate/lib/templates.js | 32 +- examples/api/amp-access/index.js | 20 +- examples/api/amp-form.js | 20 +- examples/api/autosuggest.js | 10 +- examples/api/cache.js | 4 +- examples/api/photo-stream.js | 20 +- examples/api/product-browse-page.js | 46 +- examples/api/slow-response.js | 16 +- examples/index.js | 13 +- examples/lib/SampleRenderer.js | 19 +- .../source/1.components/amp-analytics/api.js | 37 +- .../source/1.components/amp-live-list/api.js | 56 ++- .../1.components/amp-recaptcha-input/api.js | 22 +- .../1.components/amp-user-notification/api.js | 2 +- .../source/1.components/amp-web-push/api.js | 33 +- .../1.components/amp-web-push/static/sw.js | 176 ++++--- .../source/e-commerce/Checkout_Flow/api.js | 4 +- examples/source/e-commerce/Hotel/api.js | 11 +- examples/source/e-commerce/Housing/api.js | 9 +- .../source/e-commerce/Shopping_Cart/api.js | 55 ++- .../Comment_Section/api.js | 16 +- .../Favorite_Button/api.js | 2 +- .../Paged_List/api.js | 4 +- .../interactivity-dynamic-content/Poll/api.js | 34 +- .../SeatMap/api.js | 4 +- .../SeatMap_Multiple_Selection/api.js | 4 +- .../source/news-publishing/Live_Blog/api.js | 124 +++-- .../personalization/OAuth2_Login/api.js | 100 ++-- .../static/samples/files/amp_url_converter.js | 21 +- gulpfile.js/build.js | 460 ++++++++++-------- gulpfile.js/deploy.js | 81 ++- gulpfile.js/develop.js | 27 +- gulpfile.js/import.js | 52 +- gulpfile.js/index.js | 13 +- gulpfile.js/lint.js | 21 +- gulpfile.js/packager.js | 9 +- gulpfile.js/testSetup.js | 4 +- gulpfile.js/validate.js | 7 +- jest.config.js | 6 +- package-lock.json | 47 ++ package.json | 3 + pages/static/serviceworker.js | 24 +- platform/lib/amp/formatHelper.test.js | 8 +- platform/lib/common/AmpConstants.js | 6 +- platform/lib/common/filteredPage.js | 46 +- platform/lib/common/growPageLoader.js | 3 +- platform/lib/common/samples.js | 16 +- platform/lib/common/samples.test.js | 100 ++-- platform/lib/config.js | 24 +- .../lib/format-transform/formats/amp4email.js | 12 +- platform/lib/format-transform/index.js | 15 +- platform/lib/format-transform/index.test.js | 21 +- platform/lib/middleware/caching.js | 7 +- platform/lib/middleware/redirects.js | 20 +- platform/lib/middleware/redirects.test.js | 85 ++-- platform/lib/middleware/subdomain.js | 34 +- platform/lib/pipeline/blogImporter.js | 56 ++- platform/lib/pipeline/collection.js | 2 +- .../pipeline/componentReferenceImporter.js | 106 ++-- platform/lib/pipeline/gitHubImporter.js | 79 ++- platform/lib/pipeline/markdownDocument.js | 145 +++--- .../lib/pipeline/markdownDocument.test.js | 56 ++- platform/lib/pipeline/recentGuides.js | 49 +- platform/lib/pipeline/roadmapImporter.js | 100 ++-- platform/lib/pipeline/specImporter.js | 6 +- platform/lib/platform.js | 61 ++- platform/lib/routers/example/embeds.js | 11 +- platform/lib/routers/example/experiments.js | 1 - platform/lib/routers/example/sources.js | 19 +- platform/lib/routers/example/static.js | 1 - platform/lib/routers/go.js | 4 +- platform/lib/routers/go.test.js | 22 +- platform/lib/routers/growPages.js | 106 ++-- platform/lib/routers/growPages.test.js | 46 +- platform/lib/routers/inlineExamples.js | 5 +- platform/lib/routers/notFound.js | 8 +- platform/lib/routers/packager.js | 2 +- .../lib/routers/resourceFallbackHandler.js | 48 +- platform/lib/routers/robots.js | 11 +- platform/lib/routers/runtimeLog.js | 16 +- platform/lib/routers/runtimeLog.test.js | 71 +-- platform/lib/routers/search.js | 139 ++++-- platform/lib/routers/search.test.js | 389 ++++++++------- platform/lib/routers/static.js | 25 +- platform/lib/routers/templates.js | 5 +- platform/lib/routers/whoAmI.js | 4 +- platform/lib/runtime-log/HtmlFormatter.js | 2 +- .../lib/runtime-log/HtmlFormatter.test.js | 5 +- platform/lib/runtime-log/LogProvider.js | 2 +- platform/lib/runtime-log/LogProvider.test.js | 6 +- platform/lib/samples/CodeSection.js | 53 +- platform/lib/samples/CodeSection.test.js | 111 +++-- platform/lib/samples/Document.js | 14 +- platform/lib/samples/Document.test.js | 40 +- platform/lib/samples/DocumentParser.js | 94 ++-- platform/lib/samples/DocumentParser.test.js | 123 +++-- platform/lib/samples/ElementSorting.js | 18 +- platform/lib/samples/ElementSorting.test.js | 6 +- platform/lib/samples/ExampleFile.js | 24 +- platform/lib/samples/ExampleFile.test.js | 51 +- platform/lib/samples/FileName.js | 2 +- platform/lib/samples/FileName.test.js | 40 +- platform/lib/samples/index.js | 4 +- platform/lib/templates/index.js | 10 +- .../lib/tools/componentReferenceLinker.js | 95 +++- platform/lib/tools/generalReferenceLinker.js | 182 +++++-- platform/lib/tools/growReferenceChecker.js | 271 +++++++---- platform/lib/utils/HeadDedupTransformer.js | 13 +- .../lib/utils/HeadDedupTransformer.test.js | 15 +- platform/lib/utils/cacheHelpers.js | 23 +- platform/lib/utils/cheerioHelper.js | 8 +- platform/lib/utils/credentials.js | 5 +- platform/lib/utils/cssTransformer.js | 11 +- platform/lib/utils/git.js | 19 +- platform/lib/utils/googleSearch.js | 28 +- platform/lib/utils/grow.js | 11 +- platform/lib/utils/pageCache.js | 53 +- platform/lib/utils/project.js | 7 +- platform/lib/utils/sh.js | 17 +- platform/lib/utils/slugGenerator.js | 6 +- platform/lib/utils/slugGenerator.test.js | 4 +- platform/lib/utils/travis.js | 1 - playground/.eslintrc.json | 6 +- playground/backend/api.js | 35 +- playground/backend/index.js | 44 +- playground/development.js | 28 +- playground/src/app.js | 92 ++-- playground/src/auto-importer/auto-importer.js | 47 +- .../components-provider.js | 16 +- playground/src/document/controller.js | 103 ++-- playground/src/document/document.js | 43 +- playground/src/editor/editor.js | 41 +- playground/src/email-loader/email-loader.js | 28 +- playground/src/embed-mode/index.js | 2 - playground/src/error-list/error-list.js | 45 +- playground/src/events/events.js | 5 +- playground/src/filter/filter.js | 9 +- playground/src/lazy-load/base.js | 2 +- playground/src/loader/base.js | 9 +- playground/src/menu/base.js | 22 +- playground/src/preview/preview.js | 49 +- playground/src/request-idle-callback/base.js | 6 +- playground/src/runtime/detector.js | 2 +- playground/src/runtime/runtimes.js | 82 ++-- playground/src/selector/selector.js | 4 +- playground/src/service-worker/base.js | 13 +- playground/src/snackbar/base.js | 4 +- playground/src/split-pane/base.js | 17 +- playground/src/template-dialog/base.js | 64 +-- playground/src/title-updater/base.js | 4 +- playground/src/validator/validator.js | 10 +- playground/webpack.config.js | 8 +- smoke-tests/platform/Routes.test.js | 12 +- tests/grow/examples/default-settings.spec.js | 15 +- 159 files changed, 3651 insertions(+), 2326 deletions(-) create mode 100644 .prettierrc diff --git a/.eslintrc.json b/.eslintrc.json index 33ec29e131d..70c749beb1a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,12 +1,19 @@ { - "extends": "google", + "extends": [ + "google", + "prettier" + ], "parserOptions": { "ecmaVersion": 2018, "sourceType": "module" }, + "plugins": [ + "prettier" + ], "rules": { "max-len": [2, 100, { - "ignoreComments": true, + "ignoreComments": true, + "ignoreRegExpLiterals": true, "ignoreUrls": true, "tabWidth": 2 }], @@ -25,7 +32,7 @@ "argsIgnorePattern": "(^reject$|^_$)", "varsIgnorePattern": "(^_$)" }], - "quotes": [2, "single"], + "prettier/prettier": 2, "require-jsdoc": 0, "valid-jsdoc": 0, "prefer-arrow-callback": 1, diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000000..0b64337e395 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,20 @@ +{ + "bracketSpacing": false, + "singleQuote": true, + "trailingComma": "es5", + "quoteProps": "preserve", + "overrides": [ + { + "files": "*.md", + "options": {"parser": "markdown"} + }, + { + "files": [".eslintrc", ".prettierrc", "*.json"], + "options": {"parser": "json"} + }, + { + "files": [".travis.yml", "*.yaml", "*.yml"], + "options": {"parser": "yaml"} + } + ] +} diff --git a/boilerplate/backend/index.js b/boilerplate/backend/index.js index e24325b1002..f70541768b9 100644 --- a/boilerplate/backend/index.js +++ b/boilerplate/backend/index.js @@ -1,27 +1,27 @@ /** -* Copyright 2019 The AMPHTML Authors -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ + * Copyright 2019 The AMPHTML Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ const path = require('path'); const express = require('express'); // eslint-disable-next-line new-cap const playground = express.Router(); -playground.use('/boilerplate', express.static( - path.join(__dirname, '../dist'), - {extensions: ['html']}, -)); +playground.use( + '/boilerplate', + express.static(path.join(__dirname, '../dist'), {extensions: ['html']}) +); module.exports = playground; diff --git a/boilerplate/build.js b/boilerplate/build.js index d08419d886a..121892a32e8 100644 --- a/boilerplate/build.js +++ b/boilerplate/build.js @@ -37,12 +37,13 @@ function initConfig() { categories: require('./data/categories.json'), formats: require('./data/formats.json'), templates: templates.find('./templates/files'), - highlightTheme: - io.readFile(path.join(__dirname, './templates/styles/code-snippet.scss')), + highlightTheme: io.readFile( + path.join(__dirname, './templates/styles/code-snippet.scss') + ), }; // assign default template let defaultTemplate; - config.formats.forEach((format) => { + config.formats.forEach(format => { format.template = config.templates[format.id]; if (format.default) { defaultTemplate = format.template; diff --git a/boilerplate/lib/io.js b/boilerplate/lib/io.js index 1c7bfb57fd2..289b3b3be2f 100644 --- a/boilerplate/lib/io.js +++ b/boilerplate/lib/io.js @@ -20,7 +20,7 @@ const fs = require('fs'); const path = require('path'); function listFiles(currentDirPath, result = [], recursive = false) { - fs.readdirSync(currentDirPath).forEach((name) => { + fs.readdirSync(currentDirPath).forEach(name => { const filePath = path.join(currentDirPath, name); const stat = fs.statSync(filePath); if (stat.isFile() && !path.basename(filePath).startsWith('.')) { diff --git a/boilerplate/lib/templates.js b/boilerplate/lib/templates.js index 81b9d31c6b5..655ddcfe9b4 100644 --- a/boilerplate/lib/templates.js +++ b/boilerplate/lib/templates.js @@ -27,10 +27,11 @@ const TEMPLATES_DIR = '../templates'; const NODE_MODULES = '../../node_modules'; const STYLES = path.join(TEMPLATES_DIR, 'styles'); -const INCLUDE_PATHS = [FRONTEND_DIR, NODE_MODULES, STYLES].map((dir) => path.join(__dirname, dir)); +const INCLUDE_PATHS = [FRONTEND_DIR, NODE_MODULES, STYLES].map(dir => + path.join(__dirname, dir) +); - -Handlebars.registerHelper('scss', (scssPath) => { +Handlebars.registerHelper('scss', scssPath => { for (const includePath of INCLUDE_PATHS) { const templatePath = path.join(includePath, scssPath); if (io.fileExists(templatePath)) { @@ -38,7 +39,9 @@ Handlebars.registerHelper('scss', (scssPath) => { file: templatePath, includePaths: INCLUDE_PATHS, }); - return new Handlebars.SafeString(result.css.toString().replace('@charset "UTF-8";', '')); + return new Handlebars.SafeString( + result.css.toString().replace('@charset "UTF-8";', '') + ); } } throw new Error('File not found ' + scssPath); @@ -60,7 +63,7 @@ function findTemplates(dir) { Handlebars.registerPartial(partials); const icons = findPartials(path.join(dir, ICONS_DIR)); Handlebars.registerPartial(icons); - io.listFiles(dir).forEach((name) => { + io.listFiles(dir).forEach(name => { const templateName = path.basename(name, path.extname(name)); templates[templateName] = readTemplate(name); }); @@ -98,15 +101,16 @@ function replaceEndTag(match) { function findPartials(dir) { const partialFiles = io.listFiles(dir, [], true); - return partialFiles.map((f) => { - const name = f.replace(dir, ''); - const content = io.readFile(f, 'utf-8'); - return [name, content]; - }) - .reduce((obj, prop) => { - obj[prop[0]] = prop[1]; - return obj; - }, {}); + return partialFiles + .map(f => { + const name = f.replace(dir, ''); + const content = io.readFile(f, 'utf-8'); + return [name, content]; + }) + .reduce((obj, prop) => { + obj[prop[0]] = prop[1]; + return obj; + }, {}); } module.exports.find = findTemplates; module.exports.render = renderTemplate; diff --git a/examples/api/amp-access/index.js b/examples/api/amp-access/index.js index 38c48efed41..1d6df43a3cd 100644 --- a/examples/api/amp-access/index.js +++ b/examples/api/amp-access/index.js @@ -24,9 +24,11 @@ const path = require('path'); // eslint-disable-next-line new-cap const examples = express.Router(); examples.use(cookieParser()); -examples.use(express.urlencoded({ - extended: true, -})); +examples.use( + express.urlencoded({ + extended: true, + }) +); const AMP_ACCESS_COOKIE = 'ABE_LOGGED_IN'; const VALID_USERS = { @@ -36,7 +38,7 @@ const VALID_USERS = { const POWER_USERS = { 'Jane@gmail.com': true, }; -const EXPIRATION_DATE = 24*60*60*1000; // 1 day in ms +const EXPIRATION_DATE = 24 * 60 * 60 * 1000; // 1 day in ms const LOGIN_FILE_PATH = path.join(__dirname, 'login.html'); examples.get('/authorization', handleAuthorization); @@ -91,7 +93,11 @@ function handleSubmit(request, response) { response.status(401).send('Invalid email'); return; } - response.cookie(AMP_ACCESS_COOKIE, {email}, {expires: new Date(Date.now() + EXPIRATION_DATE)}); + response.cookie( + AMP_ACCESS_COOKIE, + {email}, + {expires: new Date(Date.now() + EXPIRATION_DATE)} + ); let returnUrl = request.body.returnurl; if (!isValidURL(returnUrl)) { response.status(500).send('Invalid return URL'); @@ -103,7 +109,9 @@ function handleSubmit(request, response) { function isValidURL(url) { const a = URL.parse(url); - return (a.host && a.protocol && (a.protocol === 'http:' || a.protocol === 'https:')); + return ( + a.host && a.protocol && (a.protocol === 'http:' || a.protocol === 'https:') + ); } module.exports = examples; diff --git a/examples/api/amp-form.js b/examples/api/amp-form.js index 04a0fb54cf6..c8dd6d48670 100644 --- a/examples/api/amp-form.js +++ b/examples/api/amp-form.js @@ -21,16 +21,26 @@ const upload = multer(); // eslint-disable-next-line new-cap const examples = express.Router(); -examples.use(express.urlencoded({ - extended: false, -})); +examples.use( + express.urlencoded({ + extended: false, + }) +); const ERROR_CASE_AMP_FORM = 'error'; examples.get('/submit-form', submitForm); examples.post('/submit-form-xhr', upload.none(), submitFormXHR); -examples.post('/submit-form-input-text-xhr', upload.none(), submitFormXHRInputText); -examples.post('/verify-form-input-text-xhr', upload.none(), verifyFormXHRInputText); +examples.post( + '/submit-form-input-text-xhr', + upload.none(), + submitFormXHRInputText +); +examples.post( + '/verify-form-input-text-xhr', + upload.none(), + verifyFormXHRInputText +); function submitForm(request, response) { response.redirect(303, '/static/samples/files/amp-form-success.html'); diff --git a/examples/api/autosuggest.js b/examples/api/autosuggest.js index 08148a99167..c1df2a59344 100644 --- a/examples/api/autosuggest.js +++ b/examples/api/autosuggest.js @@ -81,7 +81,7 @@ examples.post('/autosuggest/address', upload.none(), handleAddressRequest); function handleSearchRequest(request, response) { const query = request.query ? request.query.q : ''; - let results = US_CAPITAL_CITIES.filter((key) => { + let results = US_CAPITAL_CITIES.filter(key => { return key.toUpperCase().includes(query.toUpperCase()); }); @@ -89,17 +89,17 @@ function handleSearchRequest(request, response) { results = results.slice(0, MAX_RESULT_SIZE); } - const items = ({ + const items = { items: [ { query, results, }, ], - }); + }; response.json(items); -}; +} function handleAddressRequest(request, response) { const city = request.body ? request.body.city : ''; @@ -114,6 +114,6 @@ function handleAddressRequest(request, response) { response.json({ result, }); -}; +} module.exports = examples; diff --git a/examples/api/cache.js b/examples/api/cache.js index bacdaa47755..999102a677b 100644 --- a/examples/api/cache.js +++ b/examples/api/cache.js @@ -108,7 +108,9 @@ examples.get('/not-found', (request, response) => { examples.get('/redirect', (request, response) => { setImmutable(response); if (!request.query.url) { - response.status().send('No url specified via ?url=https://example.com'); + response + .status() + .send('No url specified via ?url=https://example.com'); return; } try { diff --git a/examples/api/photo-stream.js b/examples/api/photo-stream.js index c7b08412c71..ef92beef598 100644 --- a/examples/api/photo-stream.js +++ b/examples/api/photo-stream.js @@ -53,16 +53,24 @@ examples.all('/photo-stream', (req, res) => { items = items[0]; } - const nextUrl = req.baseUrl + '/photo-stream?items=' + - numberOfItems + '&left=' + JSON.stringify(pagesLeft - 1); + const nextUrl = + req.baseUrl + + '/photo-stream?items=' + + numberOfItems + + '&left=' + + JSON.stringify(pagesLeft - 1); const randomFalsy = () => { const rand = Math.floor(Math.random() * Math.floor(3)); switch (rand) { - case 1: return null; - case 2: return undefined; - case 3: return ''; - default: return false; + case 1: + return null; + case 2: + return undefined; + case 3: + return ''; + default: + return false; } }; diff --git a/examples/api/product-browse-page.js b/examples/api/product-browse-page.js index 50b7f2eb822..aced09a3340 100644 --- a/examples/api/product-browse-page.js +++ b/examples/api/product-browse-page.js @@ -20,10 +20,10 @@ const utils = require('@lib/utils'); // eslint-disable-next-line new-cap const examples = express.Router(); -const products = require( - utils.project.absolute('/examples/static/samples/json/related_products.json'), -); -const productNames = products.items.map((item) => { +const products = require(utils.project.absolute( + '/examples/static/samples/json/related_products.json' +)); +const productNames = products.items.map(item => { return item.name; }); let hasMorePages = false; @@ -35,12 +35,21 @@ examples.get('/json/more_related_products_page', handleLoadMoreRequest); function handleSearchRequest(request, response) { const searchQuery = request.query.search; - response.redirect(301, `${request.protocol}://${request.get('host')}${request.baseUrl}?SEARCH=${searchQuery}`); + response.redirect( + 301, + `${request.protocol}://${request.get('host')}${ + request.baseUrl + }?SEARCH=${searchQuery}` + ); } function handleProductsRequest(request, response) { - const productQuery = !!request.query.searchProduct ? request.query.searchProduct : ''; - const colorQuery = !!request.query.searchColor ? request.query.searchColor : ''; + const productQuery = !!request.query.searchProduct + ? request.query.searchProduct + : ''; + const colorQuery = !!request.query.searchColor + ? request.query.searchColor + : ''; // find products that match the query const responseProducts = findProducts(productQuery, colorQuery); @@ -65,9 +74,11 @@ function findProducts(name = '', color = 'all') { color = color.toLowerCase(); name = name.toLowerCase(); - return products.items.filter((prod) => { - return prod.name.toLowerCase().includes(name) && - (prod.color.toLowerCase().includes(color) || color === 'all'); + return products.items.filter(prod => { + return ( + prod.name.toLowerCase().includes(name) && + (prod.color.toLowerCase().includes(color) || color === 'all') + ); }); } @@ -75,22 +86,25 @@ function handleProductsAutosuggestRequest(request, response) { const query = request.query.q; // filter array of productnames by query - const filteredStrs = productNames.filter((desc) => { + const filteredStrs = productNames.filter(desc => { return desc.toLowerCase().includes(query.toLowerCase()); }); response.json({ - items: [{ - query, - results: filteredStrs.splice(0, 4), - }], + items: [ + { + query, + results: filteredStrs.splice(0, 4), + }, + ], }); } function handleLoadMoreRequest(request, response) { const moreItemsPageIndex = request.query.moreItemsPageIndex; const productsFile = require(utils.project.absolute( - `/examples/static/samples/json/more_related_products_page${moreItemsPageIndex}.json`)); + `/examples/static/samples/json/more_related_products_page${moreItemsPageIndex}.json` + )); hasMorePages = Number(moreItemsPageIndex) !== 1; diff --git a/examples/api/slow-response.js b/examples/api/slow-response.js index 8242dc1a487..9ec17384564 100644 --- a/examples/api/slow-response.js +++ b/examples/api/slow-response.js @@ -46,7 +46,9 @@ async function slowJson(request, response) { items: [ { // eslint-disable-next-line max-len - title: `This JSON response was delayed ${getDelay(request)} milliseconds. Hard-refresh the page (Ctrl/Cmd+Shift+R) if you didn't see the spinner.`, + title: `This JSON response was delayed ${getDelay( + request + )} milliseconds. Hard-refresh the page (Ctrl/Cmd+Shift+R) if you didn't see the spinner.`, }, ], }); @@ -55,18 +57,24 @@ async function slowJson(request, response) { async function slowJsonWithItems(request, response) { await sleep(getDelay(request)); errorIfRequested(request, response); - response.sendFile(path.join(__dirname, '../static/samples/json/related_products.json')); + response.sendFile( + path.join(__dirname, '../static/samples/json/related_products.json') + ); } async function slowIframe(request, response) { await sleep(getDelay(request)); errorIfRequested(request, response); // eslint-disable-next-line max-len - response.send(`This iframe was delayed ${getDelay(request)} milliseconds. Hard-refresh the page (Ctrl/Cmd+Shift+R) if you didn't see the spinner.`); + response.send( + `This iframe was delayed ${getDelay( + request + )} milliseconds. Hard-refresh the page (Ctrl/Cmd+Shift+R) if you didn't see the spinner.` + ); } function sleep(duration) { - return new Promise((resolve) => setTimeout(resolve, duration)); + return new Promise(resolve => setTimeout(resolve, duration)); } module.exports = examples; diff --git a/examples/index.js b/examples/index.js index 82e8cc028d7..b29266618f7 100644 --- a/examples/index.js +++ b/examples/index.js @@ -29,15 +29,16 @@ loadRouters('api', '/api'); /* auto import all routers defined in this dir */ loadRouters('source'); -function loadRouters(root, prefix='') { +function loadRouters(root, prefix = '') { const routers = []; const rootDir = resolve(join(__dirname, root)); listFiles(rootDir, routers, true); - routers.filter((path) => path.endsWith('.js') && !path.includes('/static/')) - .forEach((path) => { - const route = join(prefix, getRoute(rootDir, path)); - examples.use('/documentation/examples' + route, require(path)); - }); + routers + .filter(path => path.endsWith('.js') && !path.includes('/static/')) + .forEach(path => { + const route = join(prefix, getRoute(rootDir, path)); + examples.use('/documentation/examples' + route, require(path)); + }); } function getRoute(rootDir, filePath) { diff --git a/examples/lib/SampleRenderer.js b/examples/lib/SampleRenderer.js index c9e32604127..7bcdbee1cea 100644 --- a/examples/lib/SampleRenderer.js +++ b/examples/lib/SampleRenderer.js @@ -84,11 +84,18 @@ class SampleRenderer { * @private */ async getTemplate_(request) { - const samplePath = this.removeQuery_(request.originalUrl.substring(PREFIX_EXAMPLES.length)); + const samplePath = this.removeQuery_( + request.originalUrl.substring(PREFIX_EXAMPLES.length) + ); // If it's a preview request, load the source template - if (request.protocol + '://' + request.get('host') === config.hosts.preview.base) { + if ( + request.protocol + '://' + request.get('host') === + config.hosts.preview.base + ) { const sourcePath = join(DIR_SOURCES, samplePath + '.html'); - return Templates.get(sourcePath, () => readFileAsync(sourcePath, 'utf-8')); + return Templates.get(sourcePath, () => + readFileAsync(sourcePath, 'utf-8') + ); } // else load the documentation template return Templates.get(samplePath, () => { @@ -97,7 +104,11 @@ class SampleRenderer { return this.fetchSampleDoc_(samplePath); } else { // fetch comiled doc page from filesystem - const docFilePath = join(DIR_DOCS, PREFIX_EXAMPLES, this.appendIndexHtml_(samplePath)); + const docFilePath = join( + DIR_DOCS, + PREFIX_EXAMPLES, + this.appendIndexHtml_(samplePath) + ); return readFileAsync(docFilePath, 'utf-8'); } }); diff --git a/examples/source/1.components/amp-analytics/api.js b/examples/source/1.components/amp-analytics/api.js index 2f1dfa1c6af..c0af5f1d628 100644 --- a/examples/source/1.components/amp-analytics/api.js +++ b/examples/source/1.components/amp-analytics/api.js @@ -32,7 +32,8 @@ const USER_CHANGE_LISTENERS = {}; const GLOBAL_ANALYTICS = '__all_users__'; const EXPIRES = 60 * 60 * 24 * 365; // 1 year const embedFilePath = './embed.html'; -const analyticsTemplate = nunjucks.compile(` +const analyticsTemplate = nunjucks.compile( + ` @@ -45,13 +46,17 @@ const analyticsTemplate = nunjucks.compile(` {% else %} No data available. {% endfor %} -
Event
`.replace(/\n/g, '')); + `.replace(/\n/g, '') +); SampleRenderer.use(examples, (request, response, template) => { let user = request.cookies[AMP_ANALYTICS_COOKIE]; if (!user) { user = uuid.v4(); - response.cookie(AMP_ANALYTICS_COOKIE, user, {maxAge: EXPIRES, httpOnly: true}); + response.cookie(AMP_ANALYTICS_COOKIE, user, { + maxAge: EXPIRES, + httpOnly: true, + }); } response.send(template.render(createRequestContext(request, {user}))); }); @@ -74,20 +79,22 @@ function pingHandler(request, response) { return; } response.sendStatus(200); -}; +} function embedHandler(request, response) { const account = request.query.account; const user = request.query.user; const analytics = forUser(account, user); const host = request.protocol + '://' + request.get('host'); - response.send(nunjucks.render(embedFilePath, { - host, - account, - user, - data: analytics, - })); -}; + response.send( + nunjucks.render(embedFilePath, { + host, + account, + user, + data: analytics, + }) + ); +} function embedListenHandler(request, response) { const account = request.query.account; @@ -114,7 +121,7 @@ function embedListenHandler(request, response) { request.on('close', () => { removeUserListener(user, onNewAnalyticsData); }); -}; +} /** * Add user analytics change listener. @@ -126,7 +133,7 @@ function addUserListener(userId, callback) { USER_CHANGE_LISTENERS[userId] = listeners; } listeners.push(callback); -}; +} /** * Remove user analytics change listener. @@ -141,7 +148,7 @@ function removeUserListener(userId, callback) { return; } listeners.splice(index, 1); -}; +} /** * Returns analytics for the given user and account. @@ -195,7 +202,7 @@ function notifyListeners(user, data) { return; } const formattedData = formatData(data); - listeners.forEach((listener) => { + listeners.forEach(listener => { listener(formattedData); }); } diff --git a/examples/source/1.components/amp-live-list/api.js b/examples/source/1.components/amp-live-list/api.js index 48b6215864c..9ec84b6ef04 100644 --- a/examples/source/1.components/amp-live-list/api.js +++ b/examples/source/1.components/amp-live-list/api.js @@ -26,27 +26,57 @@ const router = express.Router(); const blogItems = [ newPost('A green landscape with trees.', 'landscape_green_1280x853.jpg', 1), - newPost('Mountains reflecting on a lake.', 'landscape_mountains_1280x657.jpg', 2), - newPost('A road leading to a lake with mountains on the back.', 'landscape_lake_1280x857.jpg', 3), - newPost('Forested hills with a grey sky in the background.', 'landscape_trees_1280x960.jpg', 4), - newPost('Scattered houses in a mountain village.', 'landscape_village_1280x853.jpg', 5), + newPost( + 'Mountains reflecting on a lake.', + 'landscape_mountains_1280x657.jpg', + 2 + ), + newPost( + 'A road leading to a lake with mountains on the back.', + 'landscape_lake_1280x857.jpg', + 3 + ), + newPost( + 'Forested hills with a grey sky in the background.', + 'landscape_trees_1280x960.jpg', + 4 + ), + newPost( + 'Scattered houses in a mountain village.', + 'landscape_village_1280x853.jpg', + 5 + ), newPost('A deep canyon.', 'landscape_canyon_1280x1700.jpg', 6), - newPost('A desert with mountains in the background.', 'landscape_desert_1280x853.jpg', 7), + newPost( + 'A desert with mountains in the background.', + 'landscape_desert_1280x853.jpg', + 7 + ), newPost('Colorful houses on a street.', 'landscape_houses_1280x803.jpg', 8), newPost('Blue sea surrounding a cave.', 'landscape_sea_1280x848.jpg', 9), - newPost('A ship sailing the sea at sunset.', 'landscape_ship_1280x853.jpg', 10), + newPost( + 'A ship sailing the sea at sunset.', + 'landscape_ship_1280x853.jpg', + 10 + ), ]; SampleRenderer.use(router, (request, response, template) => { // set max-age to 15 s - the minimum refresh time for an amp-live-list setMaxAge(response, 15); - response.send(template.render(createRequestContext(request, { - // render the current time - time: new Date().toLocaleTimeString(), - timestamp: Number(new Date()), - // send a random list of blog items to make it also work on the cache - blogItems: blogItems.filter(() => Math.floor(Math.random() * Math.floor(2))), - }))); + response.send( + template.render( + createRequestContext(request, { + // render the current time + time: new Date().toLocaleTimeString(), + timestamp: Number(new Date()), + // send a random list of blog items to make it also work on the cache + blogItems: blogItems.filter(() => + Math.floor(Math.random() * Math.floor(2)) + ), + }) + ) + ); }); function newPost(text, img, id) { diff --git a/examples/source/1.components/amp-recaptcha-input/api.js b/examples/source/1.components/amp-recaptcha-input/api.js index 003799bf10f..8921f4b8635 100644 --- a/examples/source/1.components/amp-recaptcha-input/api.js +++ b/examples/source/1.components/amp-recaptcha-input/api.js @@ -24,9 +24,11 @@ const credentials = require('@lib/utils/credentials'); // eslint-disable-next-line new-cap const examples = express.Router(); examples.use(express.json()); -examples.use(express.urlencoded({ - extended: true, -})); +examples.use( + express.urlencoded({ + extended: true, + }) +); const RECAPTCHA_SECRET = 'recaptcha_secret'; @@ -55,14 +57,18 @@ examples.post('/api/recaptcha', upload.array(), async (req, res) => { params.append('response', req.body.recaptcha_token); params.append('remoteip', req.ip); - const response = await fetch('https://www.google.com/recaptcha/api/siteverify', { - method: 'POST', - body: params, - }).then((response) => response.json()); + const response = await fetch( + 'https://www.google.com/recaptcha/api/siteverify', + { + method: 'POST', + body: params, + } + ).then(response => response.json()); if (!response.success) { res.status(500).json({ - message: 'Error on recaptcha server verification. Uncessful response from recaptcha.', + message: + 'Error on recaptcha server verification. Uncessful response from recaptcha.', }); return; } diff --git a/examples/source/1.components/amp-user-notification/api.js b/examples/source/1.components/amp-user-notification/api.js index a3d15fcff19..711a60de5b7 100644 --- a/examples/source/1.components/amp-user-notification/api.js +++ b/examples/source/1.components/amp-user-notification/api.js @@ -70,6 +70,6 @@ function dismissNotification(notificationId, userId) { DISMISSED_NOTIFICATIONS[userId] = user; } user[notificationId] = true; -}; +} module.exports = examples; diff --git a/examples/source/1.components/amp-web-push/api.js b/examples/source/1.components/amp-web-push/api.js index 12660f1d226..25acaac4900 100644 --- a/examples/source/1.components/amp-web-push/api.js +++ b/examples/source/1.components/amp-web-push/api.js @@ -22,9 +22,11 @@ const webPush = require('web-push'); // eslint-disable-next-line new-cap const examples = express.Router(); -examples.use(express.urlencoded({ - extended: false, -})); +examples.use( + express.urlencoded({ + extended: false, + }) +); examples.use(express.json()); examples.use(express.static(path.join(__dirname, 'static'))); @@ -35,20 +37,21 @@ examples.post('/send-push', (request, response) => { const payload = 'amp-web-push rocks!'; webPush.setGCMAPIKey( - 'BGSMEuZxEhw1gWd5A08e5pJa18_OozPds_k8U0VedjvbrQlyVWCSXzx_tft8ap6KIrHiewiUwO--v56m0g30uCc'); + 'BGSMEuZxEhw1gWd5A08e5pJa18_OozPds_k8U0VedjvbrQlyVWCSXzx_tft8ap6KIrHiewiUwO--v56m0g30uCc' + ); webPush.setVapidDetails( - 'mailto:drenzulli@google.com', - 'BA99vy78Qu4vuByBMUZ1W5J0H7ngllFJhF9GcjbS_GJM9iD7uXIm-dQj7nXvisXHI6372ga3mZR3kFdS9MYTdSA', - 'zrilyCmdC3EqCUA4g4u0JP5jafJrst8kw4TeFGI3bSI', + 'mailto:drenzulli@google.com', + 'BA99vy78Qu4vuByBMUZ1W5J0H7ngllFJhF9GcjbS_GJM9iD7uXIm-dQj7nXvisXHI6372ga3mZR3kFdS9MYTdSA', + 'zrilyCmdC3EqCUA4g4u0JP5jafJrst8kw4TeFGI3bSI' ); - webPush.sendNotification( - pushSubscription, - payload, - ).then(() => { - response.json({result: 'Web Push received!'}); - }).catch((error) => { - response.status(500).send('Web Push Failed: ' + error); - }); + webPush + .sendNotification(pushSubscription, payload) + .then(() => { + response.json({result: 'Web Push received!'}); + }) + .catch(error => { + response.status(500).send('Web Push Failed: ' + error); + }); }); module.exports = examples; diff --git a/examples/source/1.components/amp-web-push/static/sw.js b/examples/source/1.components/amp-web-push/static/sw.js index 9a6e09c3690..badffd66cbf 100644 --- a/examples/source/1.components/amp-web-push/static/sw.js +++ b/examples/source/1.components/amp-web-push/static/sw.js @@ -17,7 +17,7 @@ importScripts('https://cdn.rawgit.com/jakearchibald/idb/97e4e878/lib/idb.js'); const applicationServerPublicKey = -'BA99vy78Qu4vuByBMUZ1W5J0H7ngllFJhF9GcjbS_GJM9iD7uXIm-dQj7nXvisXHI6372ga3mZR3kFdS9MYTdSA'; + 'BA99vy78Qu4vuByBMUZ1W5J0H7ngllFJhF9GcjbS_GJM9iD7uXIm-dQj7nXvisXHI6372ga3mZR3kFdS9MYTdSA'; const convertedVapidKey = urlB64ToUint8Array(applicationServerPublicKey); const WEB_PUSH_DB = 'web-push-db'; const WEB_PUSH_SUBSCRIPTION = 'web-push-subscription'; @@ -60,7 +60,7 @@ const WorkerMessengerCommand = { service worker for events that aren't known for sure to be listened for. Also see: https://github.com/w3c/ServiceWorker/issues/1156 */ -self.addEventListener('message', (event) => { +self.addEventListener('message', event => { /* Messages sent from amp-web-push have the format: - command: A string describing the message topic (e.g. @@ -88,26 +88,30 @@ self.addEventListener('message', (event) => { */ function onMessageReceivedSubscriptionState() { let retrievedPushSubscription = null; - self.registration.pushManager.getSubscription() - .then((pushSubscription) => { - retrievedPushSubscription = pushSubscription; - if (!pushSubscription) { - return null; - } else { - return self.registration.pushManager.permissionState( - pushSubscription.options, - ); - } - }).then((permissionStateOrNull) => { - if (permissionStateOrNull == null) { - broadcastReply(WorkerMessengerCommand.AMP_SUBSCRIPTION_STATE, false); - } else { - const isSubscribed = !!retrievedPushSubscription && - permissionStateOrNull === 'granted'; - broadcastReply(WorkerMessengerCommand.AMP_SUBSCRIPTION_STATE, - isSubscribed); - } - }); + self.registration.pushManager + .getSubscription() + .then(pushSubscription => { + retrievedPushSubscription = pushSubscription; + if (!pushSubscription) { + return null; + } else { + return self.registration.pushManager.permissionState( + pushSubscription.options + ); + } + }) + .then(permissionStateOrNull => { + if (permissionStateOrNull == null) { + broadcastReply(WorkerMessengerCommand.AMP_SUBSCRIPTION_STATE, false); + } else { + const isSubscribed = + !!retrievedPushSubscription && permissionStateOrNull === 'granted'; + broadcastReply( + WorkerMessengerCommand.AMP_SUBSCRIPTION_STATE, + isSubscribed + ); + } + }); } /** @@ -126,13 +130,15 @@ function onMessageReceivedSubscribe() { https://github.com/web-push-libs/web-push, convert the VAPID key to a UInt8 array and supply it to applicationServerKey */ - self.registration.pushManager.subscribe({ - userVisibleOnly: true, - applicationServerKey: convertedVapidKey, - }).then((pushSubscription) => { - persistSubscriptionLocally(pushSubscription); - broadcastReply(WorkerMessengerCommand.AMP_SUBSCRIBE, null); - }); + self.registration.pushManager + .subscribe({ + userVisibleOnly: true, + applicationServerKey: convertedVapidKey, + }) + .then(pushSubscription => { + persistSubscriptionLocally(pushSubscription); + broadcastReply(WorkerMessengerCommand.AMP_SUBSCRIBE, null); + }); } /** @@ -140,13 +146,14 @@ function onMessageReceivedSubscribe() { The broadcast value is null (not used in the AMP page). */ function onMessageReceivedUnsubscribe() { - self.registration.pushManager.getSubscription() - .then((subscription) => subscription.unsubscribe()) - .then(() => { - clearLocalDatabase(); - // OPTIONALLY IMPLEMENT: Forward the unsubscription to your server here - broadcastReply(WorkerMessengerCommand.AMP_UNSUBSCRIBE, null); - }); + self.registration.pushManager + .getSubscription() + .then(subscription => subscription.unsubscribe()) + .then(() => { + clearLocalDatabase(); + // OPTIONALLY IMPLEMENT: Forward the unsubscription to your server here + broadcastReply(WorkerMessengerCommand.AMP_UNSUBSCRIBE, null); + }); } /** @@ -155,18 +162,16 @@ function onMessageReceivedUnsubscribe() { * @param {!JsonObject} payload */ function broadcastReply(command, payload) { - self.clients.matchAll() - .then((clients) => { - for (const client of clients) { - client. /* OK*/ postMessage({ - command, - payload, - }); - } + self.clients.matchAll().then(clients => { + for (const client of clients) { + client./* OK*/ postMessage({ + command, + payload, }); + } + }); } - /** This section of the ServiceWorker deals with SW events: initializing the DB where tokens will be persisted, sending the request for push notifcations to the endpoint, and listening to push events. @@ -181,21 +186,23 @@ self.addEventListener('install', () => self.skipWaiting()); Creates the DB to store subscription objects and calls clients.claim(), to allow an active service worker to set itself as the controller for all clients within its scope. */ -self.addEventListener('activate', (event) => { - event.waitUntil((async () => { - await idb.open(WEB_PUSH_DB, 1, (upgradeDB) => { - upgradeDB.createObjectStore(WEB_PUSH_SUBSCRIPTION, { - keyPath: 'id', +self.addEventListener('activate', event => { + event.waitUntil( + (async () => { + await idb.open(WEB_PUSH_DB, 1, upgradeDB => { + upgradeDB.createObjectStore(WEB_PUSH_SUBSCRIPTION, { + keyPath: 'id', + }); }); - }); - self.clients.claim(); - })()); + self.clients.claim(); + })() + ); }); /** Listens to push events, and displays a notification, using the payload text. */ -self.addEventListener('push', (event) => { +self.addEventListener('push', event => { const options = { body: event.data.text(), vibrate: [100, 50, 100], @@ -205,7 +212,7 @@ self.addEventListener('push', (event) => { }, }; event.waitUntil( - self.registration.showNotification('Push Notification', options), + self.registration.showNotification('Push Notification', options) ); }); @@ -214,30 +221,41 @@ self.addEventListener('push', (event) => { so WebPush messages can be sent. Any other request goes to the network directly. */ -self.addEventListener('fetch', (event) => { - if (event.request.url.includes('/documentation/examples/components/amp-web-push/send-push')) { - event.respondWith((async () => { - const db = await idb.open(WEB_PUSH_DB, 1); - const tx = db.transaction([WEB_PUSH_SUBSCRIPTION], 'readonly'); - const store = tx.objectStore(WEB_PUSH_SUBSCRIPTION); +self.addEventListener('fetch', event => { + if ( + event.request.url.includes( + '/documentation/examples/components/amp-web-push/send-push' + ) + ) { + event.respondWith( + (async () => { + const db = await idb.open(WEB_PUSH_DB, 1); + const tx = db.transaction([WEB_PUSH_SUBSCRIPTION], 'readonly'); + const store = tx.objectStore(WEB_PUSH_SUBSCRIPTION); - const subscriptionJSON = await store.get(WEB_PUSH_SUBSCRIPTION_ID); - const options = { - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - }, - body: subscriptionJSON.data, - }; + const subscriptionJSON = await store.get(WEB_PUSH_SUBSCRIPTION_ID); + const options = { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + body: subscriptionJSON.data, + }; - return fetch(self.location.origin + - '/documentation/examples/components/amp-web-push/send-push', options); - })()); + return fetch( + self.location.origin + + '/documentation/examples/components/amp-web-push/send-push', + options + ); + })() + ); } else { - event.respondWith((async () => { - return fetch(event.request); - })()); + event.respondWith( + (async () => { + return fetch(event.request); + })() + ); } }); @@ -272,10 +290,10 @@ async function clearLocalDatabase() { Helper method to convert the VAPID key to a UInt8 array and supply it to applicationServerKey. */ function urlB64ToUint8Array(base64String) { - const padding = '='.repeat((4 - base64String.length % 4) % 4); + const padding = '='.repeat((4 - (base64String.length % 4)) % 4); const base64 = (base64String + padding) - .replace(/\-/g, '+') - .replace(/_/g, '/'); + .replace(/\-/g, '+') + .replace(/_/g, '/'); const rawData = self.atob(base64); const outputArray = new Uint8Array(rawData.length); diff --git a/examples/source/e-commerce/Checkout_Flow/api.js b/examples/source/e-commerce/Checkout_Flow/api.js index bc98f879738..8af50e4782d 100644 --- a/examples/source/e-commerce/Checkout_Flow/api.js +++ b/examples/source/e-commerce/Checkout_Flow/api.js @@ -47,7 +47,9 @@ function handleShoppingCart(request, response) { function writeShoppingCart(request, response, clientId) { const discount = discounts.get(clientId) || 0; - const total = (SHOPPING_CART_TOTAL - SHOPPING_CART_TOTAL * discount).toFixed(2); + const total = (SHOPPING_CART_TOTAL - SHOPPING_CART_TOTAL * discount).toFixed( + 2 + ); const cart = { items: [ { diff --git a/examples/source/e-commerce/Hotel/api.js b/examples/source/e-commerce/Hotel/api.js index 92efc1c6185..543cf45589b 100644 --- a/examples/source/e-commerce/Hotel/api.js +++ b/examples/source/e-commerce/Hotel/api.js @@ -18,7 +18,9 @@ const express = require('express'); const {setMaxAge} = require('@lib/utils/cacheHelpers'); const utils = require('@lib/utils'); -const rooms = require(utils.project.absolute('/examples/static/samples/json/rooms.json')); +const rooms = require(utils.project.absolute( + '/examples/static/samples/json/rooms.json' +)); // eslint-disable-next-line new-cap const examples = express.Router(); @@ -35,8 +37,11 @@ examples.get('/rooms', (request, response) => { let result = rooms; if (arriving && leaving) { - result = rooms.filter((room) => { - return !(arriving <= new Date(room.booked.to) && leaving >= new Date(room.booked.from)); + result = rooms.filter(room => { + return !( + arriving <= new Date(room.booked.to) && + leaving >= new Date(room.booked.from) + ); }); } diff --git a/examples/source/e-commerce/Housing/api.js b/examples/source/e-commerce/Housing/api.js index aba3edeebbc..16b577edc7c 100644 --- a/examples/source/e-commerce/Housing/api.js +++ b/examples/source/e-commerce/Housing/api.js @@ -33,11 +33,16 @@ examples.get('/calculate-mortgage-xhr', (request, response) => { }); function calculateMonthlyPayment(mortgageForm = {}) { - const monthlyInterestRateDecimal = (mortgageForm.interest / 12) / 100; + const monthlyInterestRateDecimal = mortgageForm.interest / 12 / 100; const numberOfMonthlyPayments = mortgageForm.period * 12; const amountBorrowed = mortgageForm.price - mortgageForm.deposit; // eslint-disable-next-line max-len - return ((monthlyInterestRateDecimal * amountBorrowed * Math.pow((1+monthlyInterestRateDecimal), numberOfMonthlyPayments)) / (Math.pow((1+monthlyInterestRateDecimal), numberOfMonthlyPayments) - 1)).toFixed(2); + return ( + (monthlyInterestRateDecimal * + amountBorrowed * + Math.pow(1 + monthlyInterestRateDecimal, numberOfMonthlyPayments)) / + (Math.pow(1 + monthlyInterestRateDecimal, numberOfMonthlyPayments) - 1) + ).toFixed(2); } function parseMortgageForm(request) { diff --git a/examples/source/e-commerce/Shopping_Cart/api.js b/examples/source/e-commerce/Shopping_Cart/api.js index 735df6a2278..f11d1f69979 100644 --- a/examples/source/e-commerce/Shopping_Cart/api.js +++ b/examples/source/e-commerce/Shopping_Cart/api.js @@ -25,12 +25,14 @@ const examples = express.Router(); examples.use(formidableMiddleware()); examples.use(cookieParser()); -examples.use(sessions({ - cookieName: 'session', - secret: 'ampdev-shopping-cart', - duration: 24 * 60 * 60 * 1000, // 24 hours - activeDuration: 1000 * 60 * 5, // 5 minutes -})); +examples.use( + sessions({ + cookieName: 'session', + secret: 'ampdev-shopping-cart', + duration: 24 * 60 * 60 * 1000, // 24 hours + activeDuration: 1000 * 60 * 5, // 5 minutes + }) +); examples.post('/add-to-cart', addToCartHandlerPost); examples.get('/add-to-cart', addToCartHandlerGet); @@ -45,24 +47,39 @@ function addToCartHandlerPost(req, res) { if (req.headers['amp-same-origin'] !== 'true') { // transfrom POST into GET and redirect to /add_to_cart, to complete the request from the origin, with access to the session. const queryString = new URLSearchParams({...cartItem, origin}).toString(); - res.header('AMP-Redirect-To', req.protocol + '://' + req.get('host') + - `/documentation/examples/e-commerce/add_to_cart?${queryString}`); + res.header( + 'AMP-Redirect-To', + req.protocol + + '://' + + req.get('host') + + `/documentation/examples/e-commerce/add_to_cart?${queryString}` + ); } else { // Complete the request from the origin updateShoppingCartOnSession(req, cartItem); - res.header('AMP-Redirect-To', req.protocol + '://' + req.get('host') + - '/documentation/examples/e-commerce/shopping_cart/'); + res.header( + 'AMP-Redirect-To', + req.protocol + + '://' + + req.get('host') + + '/documentation/examples/e-commerce/shopping_cart/' + ); } res.json({}); -}; +} function addToCartHandlerGet(req, res) { const cartItem = createCartItem(req); updateShoppingCartOnSession(req, cartItem); - res.redirect(req.protocol + '://' + req.get('host') + '/documentation/examples/e-commerce/shopping_cart/'); -}; + res.redirect( + req.protocol + + '://' + + req.get('host') + + '/documentation/examples/e-commerce/shopping_cart/' + ); +} function createCartItem(req) { return { @@ -91,7 +108,7 @@ function cartItemsHandler(req, res) { shoppingCartResponse.items.push(shoppingCart); res.send(shoppingCartResponse); -}; +} function deleteCartItemHandler(req, res) { const id = req.fields.id; @@ -109,7 +126,7 @@ function deleteCartItemHandler(req, res) { } res.send(shoppingCartResponse); -}; +} function updateShoppingCartOnSession(req, cartItem) { let shoppingCart = req.session.shoppingCart; @@ -132,8 +149,8 @@ class ShoppingCart { addItem(item) { // check if item exists in cart before pushing - const foundItem = this.cartItems.filter((elem) => { - return (elem.id == item.id && elem.size == item.size); + const foundItem = this.cartItems.filter(elem => { + return elem.id == item.id && elem.size == item.size; }); if (foundItem.length > 0) { @@ -143,7 +160,7 @@ class ShoppingCart { } this.isEmpty = false; - }; + } removeItem(id, size) { for (let i = 0; i < this.cartItems.length; i++) { @@ -156,7 +173,7 @@ class ShoppingCart { } } } - }; + } } module.exports = examples; diff --git a/examples/source/interactivity-dynamic-content/Comment_Section/api.js b/examples/source/interactivity-dynamic-content/Comment_Section/api.js index 2cba89849c1..86f913f046a 100644 --- a/examples/source/interactivity-dynamic-content/Comment_Section/api.js +++ b/examples/source/interactivity-dynamic-content/Comment_Section/api.js @@ -30,7 +30,7 @@ const config = { const commentsCache = new LRU(config); class Comment { - constructor(text, user, date=new Date().getTime()) { + constructor(text, user, date = new Date().getTime()) { this.text = text; this.user = user; this.date = new Date(date); @@ -39,8 +39,16 @@ class Comment { const USER = 'Mark'; const defaultComments = [ - new Comment('This is the first comment', 'Alice', new Date('2006-01-02 15:04')), - new Comment('This is the second comment', 'Bob', new Date('2006-01-02 15:34')), + new Comment( + 'This is the first comment', + 'Alice', + new Date('2006-01-02 15:04') + ), + new Comment( + 'This is the second comment', + 'Bob', + new Date('2006-01-02 15:34') + ), ]; examples.get('/comments', handleComments); @@ -72,7 +80,7 @@ function loadComments(request) { } const comments = commentsCache.get(cookie); if (comments) { - return comments.map((comment) => { + return comments.map(comment => { return new Comment(comment.text, comment.user, comment.date); }); } diff --git a/examples/source/interactivity-dynamic-content/Favorite_Button/api.js b/examples/source/interactivity-dynamic-content/Favorite_Button/api.js index 46e221e8bc8..97a3e75abe0 100644 --- a/examples/source/interactivity-dynamic-content/Favorite_Button/api.js +++ b/examples/source/interactivity-dynamic-content/Favorite_Button/api.js @@ -26,7 +26,7 @@ const examples = express.Router(); examples.use(cookieParser()); const AMP_FAVORITE_COOKIE = 'amp-favorite'; const AMP_FAVORITE_COUNT_COOKIE = 'amp-favorite-with-count'; -const EXPIRATION_DATE = 365*24*60*60*1000; // 365 days in ms +const EXPIRATION_DATE = 365 * 24 * 60 * 60 * 1000; // 365 days in ms examples.all('/favorite', upload.none(), (request, response) => { setMaxAge(response, 0); diff --git a/examples/source/interactivity-dynamic-content/Paged_List/api.js b/examples/source/interactivity-dynamic-content/Paged_List/api.js index b168b714b78..4f603e4b486 100644 --- a/examples/source/interactivity-dynamic-content/Paged_List/api.js +++ b/examples/source/interactivity-dynamic-content/Paged_List/api.js @@ -54,10 +54,10 @@ function generatePagedResponse(page) { }; for (let i = 0; i < ITEMS_PER_PAGE; i++) { - const itemIndex = ITEMS_PER_PAGE*(page-1) + i + 1; + const itemIndex = ITEMS_PER_PAGE * (page - 1) + i + 1; const productListing = { title: `Food ${itemIndex}`, - image: IMAGES[itemIndex % IMAGES.length - 1], + image: IMAGES[(itemIndex % IMAGES.length) - 1], copy: `Lorem ipsum dolor sit ${itemIndex} amet consequitur sine nice fun`, }; response.products.push(productListing); diff --git a/examples/source/interactivity-dynamic-content/Poll/api.js b/examples/source/interactivity-dynamic-content/Poll/api.js index 9ba87267c6a..d15c8c250c1 100644 --- a/examples/source/interactivity-dynamic-content/Poll/api.js +++ b/examples/source/interactivity-dynamic-content/Poll/api.js @@ -27,10 +27,12 @@ const examples = express.Router(); examples.use(cookieParser()); // eslint-disable-next-line max-len -const ALREADY_VOTED_MESSAGE = 'You have already answered this poll. If you want to run this sample again, use an incognito window.'; +const ALREADY_VOTED_MESSAGE = + 'You have already answered this poll. If you want to run this sample again,' + + 'use an incognito window.'; const THANKS_MESSAGE = 'Thanks for answering the poll!'; const POLL_COOKIE_NAME = 'POLL_TAKEN'; -const EXPIRATION_DATE = 365*24*60*60*1000; // 365 days in ms +const EXPIRATION_DATE = 365 * 24 * 60 * 60 * 1000; // 365 days in ms const POLL_QUESTIONS = ['Penguins', 'Ostriches', 'Kiwis', 'Wekas']; const POLL_ENTITY_TYPE = 'Poll example'; const POLL_ENTITY_NAME = 'poll'; @@ -50,20 +52,23 @@ examples.post('/submit-poll', upload.none(), async (request, response) => { } // calculate poll results and check if user has already voted. - const pollResults = await calculatePollResults(answer, clientId, request, response); - - response.json( - pollResults, + const pollResults = await calculatePollResults( + answer, + clientId, + request, + response ); + + response.json(pollResults); }); async function calculatePollResults(answer, clientId, request, response) { // get poll const pollKey = datastore.key([POLL_ENTITY_TYPE, POLL_ENTITY_NAME]); const pollEntity = await getFromDatastore(pollKey); - const pollExistingAnswers = !!pollEntity ? - pollEntity.results : - new Array(POLL_QUESTIONS.length).fill(0); + const pollExistingAnswers = !!pollEntity + ? pollEntity.results + : new Array(POLL_QUESTIONS.length).fill(0); // check if user has already voted by cookie const cookie = request.cookies[POLL_COOKIE_NAME]; if (cookie && cookie.clientId === clientId) { @@ -77,7 +82,11 @@ async function calculatePollResults(answer, clientId, request, response) { return createPollResult(pollExistingAnswers, ALREADY_VOTED_MESSAGE); } // set cookie and store clientId - response.cookie(POLL_COOKIE_NAME, {clientId}, {expires: new Date(Date.now() + EXPIRATION_DATE)}); + response.cookie( + POLL_COOKIE_NAME, + {clientId}, + {expires: new Date(Date.now() + EXPIRATION_DATE)} + ); existingClientIDs.push(clientId); try { await saveToDatastore(clientIdKey, {voters: existingClientIDs}); @@ -103,7 +112,8 @@ function createPollResult(pollResults, message) { // calculate poll results const calculatedAnswers = pollResults.map((votes, i) => { - const percentage = totalVotes > 0 ? Math.round(votes/totalVotes*100) : 0; + const percentage = + totalVotes > 0 ? Math.round((votes / totalVotes) * 100) : 0; return { 'Votes': votes, 'Percentage': new Array(percentage).fill(0), // fill array with corresponding amount of 0's to the percentage of answer @@ -134,7 +144,7 @@ function saveToDatastore(key, data) { data, }; return new Promise((resolve, reject) => { - datastore.save(entity, (e) => { + datastore.save(entity, e => { if (e) { reject(e); return; diff --git a/examples/source/interactivity-dynamic-content/SeatMap/api.js b/examples/source/interactivity-dynamic-content/SeatMap/api.js index 46d87248684..2bad81cc1ad 100644 --- a/examples/source/interactivity-dynamic-content/SeatMap/api.js +++ b/examples/source/interactivity-dynamic-content/SeatMap/api.js @@ -20,7 +20,9 @@ const express = require('express'); const SampleRenderer = require('@examples/lib/SampleRenderer'); const {createRequestContext} = require('@lib/templates/index.js'); const utils = require('@lib/utils'); -const seats = require(utils.project.absolute('/examples/static/samples/json/seats.json')); +const seats = require(utils.project.absolute( + '/examples/static/samples/json/seats.json' +)); // eslint-disable-next-line new-cap const router = express.Router(); diff --git a/examples/source/interactivity-dynamic-content/SeatMap_Multiple_Selection/api.js b/examples/source/interactivity-dynamic-content/SeatMap_Multiple_Selection/api.js index 46d87248684..2bad81cc1ad 100644 --- a/examples/source/interactivity-dynamic-content/SeatMap_Multiple_Selection/api.js +++ b/examples/source/interactivity-dynamic-content/SeatMap_Multiple_Selection/api.js @@ -20,7 +20,9 @@ const express = require('express'); const SampleRenderer = require('@examples/lib/SampleRenderer'); const {createRequestContext} = require('@lib/templates/index.js'); const utils = require('@lib/utils'); -const seats = require(utils.project.absolute('/examples/static/samples/json/seats.json')); +const seats = require(utils.project.absolute( + '/examples/static/samples/json/seats.json' +)); // eslint-disable-next-line new-cap const router = express.Router(); diff --git a/examples/source/news-publishing/Live_Blog/api.js b/examples/source/news-publishing/Live_Blog/api.js index 33e0150d7af..b510660d49f 100644 --- a/examples/source/news-publishing/Live_Blog/api.js +++ b/examples/source/news-publishing/Live_Blog/api.js @@ -30,32 +30,77 @@ const MAX_BLOG_ITEMS_NUMBER_PER_PAGE = 5; const BLOG_ID_PREFIX = 'post'; const IMG_PATH = '/static/samples/img/'; const AMP_LIVE_LIST_COOKIE_NAME = 'ABE_AMP_LIVE_LIST_STATUS'; -const EXPIRATION_DATE = 24*60*60*1000; // 1 day in ms +const EXPIRATION_DATE = 24 * 60 * 60 * 1000; // 1 day in ms const blogs = [ - newPost('Green landscape', 'A green landscape with trees.', 'landscape_green_1280x853.jpg', 1), - newPost('Mountains', 'Mountains reflecting on a lake.', 'landscape_mountains_1280x657.jpg', 2), - newPost('Road leading to a lake', 'A road leading to a lake with mountains on the back.', - 'landscape_lake_1280x857.jpg', 3), - newPost('Forested hills', 'Forested hills with a grey sky in the background.', - 'landscape_trees_1280x960.jpg', 4), - newPost('Scattered houses', 'Scattered houses in a mountain village.', - 'landscape_village_1280x853.jpg', 5), + newPost( + 'Green landscape', + 'A green landscape with trees.', + 'landscape_green_1280x853.jpg', + 1 + ), + newPost( + 'Mountains', + 'Mountains reflecting on a lake.', + 'landscape_mountains_1280x657.jpg', + 2 + ), + newPost( + 'Road leading to a lake', + 'A road leading to a lake with mountains on the back.', + 'landscape_lake_1280x857.jpg', + 3 + ), + newPost( + 'Forested hills', + 'Forested hills with a grey sky in the background.', + 'landscape_trees_1280x960.jpg', + 4 + ), + newPost( + 'Scattered houses', + 'Scattered houses in a mountain village.', + 'landscape_village_1280x853.jpg', + 5 + ), newPost('Canyon', 'A deep canyon.', 'landscape_canyon_1280x1700.jpg', 6), - newPost('Desert', 'A desert with mountains in the background.', - 'landscape_desert_1280x853.jpg', 7), - newPost('Houses', 'Colorful houses on a street.', 'landscape_houses_1280x803.jpg', 8), - newPost('Blue sea', 'Blue sea surrounding a cave.', 'landscape_sea_1280x848.jpg', 9), - newPost('Sailing ship', 'A ship sailing the sea at sunset.', 'landscape_ship_1280x853.jpg', 10), + newPost( + 'Desert', + 'A desert with mountains in the background.', + 'landscape_desert_1280x853.jpg', + 7 + ), + newPost( + 'Houses', + 'Colorful houses on a street.', + 'landscape_houses_1280x803.jpg', + 8 + ), + newPost( + 'Blue sea', + 'Blue sea surrounding a cave.', + 'landscape_sea_1280x848.jpg', + 9 + ), + newPost( + 'Sailing ship', + 'A ship sailing the sea at sunset.', + 'landscape_ship_1280x853.jpg', + 10 + ), ]; SampleRenderer.use(router, async (request, response, template) => { // set max-age to 15 s - the minimum refresh time for an amp-live-list setMaxAge(response, 15); const newStatus = await updateStatus(request, response); - response.send(template.render(createRequestContext( - request, - await createLiveBlogSample(request, newStatus), - ))); + response.send( + template.render( + createRequestContext( + request, + await createLiveBlogSample(request, newStatus) + ) + ) + ); }); async function createLiveBlogSample(request, newStatus = 0) { @@ -67,9 +112,14 @@ async function createLiveBlogSample(request, newStatus = 0) { const blogItems = await getBlogEntries(newStatus); const firstBlogId = await getFirstBlogId(request); const firstItemIndex = await getBlogEntryIndexFromID(firstBlogId, blogItems); - const lengthCurrentPageBlog = Math.min(blogItems.length, - firstItemIndex+MAX_BLOG_ITEMS_NUMBER_PER_PAGE); - const nextPageId = await getNextPageId(firstItemIndex+MAX_BLOG_ITEMS_NUMBER_PER_PAGE, blogItems); + const lengthCurrentPageBlog = Math.min( + blogItems.length, + firstItemIndex + MAX_BLOG_ITEMS_NUMBER_PER_PAGE + ); + const nextPageId = await getNextPageId( + firstItemIndex + MAX_BLOG_ITEMS_NUMBER_PER_PAGE, + blogItems + ); const prevPageId = await getPrevPageId(firstItemIndex); const prefix = await buildPrefixPaginationUrl(origin, request.baseUrl); const nextPageUrl = await buildPaginationURL(prefix, nextPageId); @@ -105,9 +155,9 @@ function readStatus(request) { function writeStatus(response, newValue) { response.cookie( - AMP_LIVE_LIST_COOKIE_NAME, - {value: newValue}, - {expires: new Date(Date.now() + EXPIRATION_DATE)}, + AMP_LIVE_LIST_COOKIE_NAME, + {value: newValue}, + {expires: new Date(Date.now() + EXPIRATION_DATE)} ); } @@ -115,12 +165,12 @@ function getBlogEntries(size) { const returnArray = []; for (let i = 0; i < size; i++) { returnArray.push( - newPost( - blogs[i].heading, - blogs[i].text, - blogs[i].img.split(IMG_PATH)[1], - i + 1, - ), + newPost( + blogs[i].heading, + blogs[i].text, + blogs[i].img.split(IMG_PATH)[1], + i + 1 + ) ); } return returnArray; @@ -138,7 +188,7 @@ function buildPaginationURL(urlPrefix, pageId) { } function getPageNumberFromProductIndex(index) { - return (index / MAX_BLOG_ITEMS_NUMBER_PER_PAGE) + 1; + return index / MAX_BLOG_ITEMS_NUMBER_PER_PAGE + 1; } function getBlogEntryIndexFromID(id, blogItems = []) { @@ -157,15 +207,19 @@ function getNextPageId(nextPageFirstItemIndex, blogItems = []) { function getPrevPageId(firstItemIndex) { if (firstItemIndex >= MAX_BLOG_ITEMS_NUMBER_PER_PAGE) { - return `${BLOG_ID_PREFIX}${firstItemIndex-MAX_BLOG_ITEMS_NUMBER_PER_PAGE+1}`; + return `${BLOG_ID_PREFIX}${firstItemIndex - + MAX_BLOG_ITEMS_NUMBER_PER_PAGE + + 1}`; } return ''; } function getFirstBlogId(request) { - const firstItemIndex = !!request.query.from ? request.query.from : `${BLOG_ID_PREFIX}1`; + const firstItemIndex = !!request.query.from + ? request.query.from + : `${BLOG_ID_PREFIX}1`; return Number(firstItemIndex.split(BLOG_ID_PREFIX)[1]); -}; +} function newPost(heading, text, img, id) { return { @@ -185,7 +239,7 @@ function createMetaData(origin, baseUrl) { result.push({ '@type': 'BlogPosting', 'headline': blogs[i].heading, - 'url': urlPrefix + BLOG_ID_PREFIX + (i+1), + 'url': urlPrefix + BLOG_ID_PREFIX + (i + 1), 'datePublished': blogs[i].timestamp, 'author': { '@type': 'Person', diff --git a/examples/source/personalization/OAuth2_Login/api.js b/examples/source/personalization/OAuth2_Login/api.js index c0a4db44a13..e59107f1bd3 100644 --- a/examples/source/personalization/OAuth2_Login/api.js +++ b/examples/source/personalization/OAuth2_Login/api.js @@ -38,21 +38,21 @@ const OAUTH_COOKIE_NAME = 'oauth2_cookie'; const USERINFO_ENDPOINT = { google: { url: 'https://www.googleapis.com/oauth2/v3/userinfo', - headers: (token) => { + headers: token => { return `Bearer ${token}`; }, propertyName: 'name', }, github: { url: 'https://api.github.com/user', - headers: (token) => { + headers: token => { return `token ${token}`; }, propertyName: 'login', }, facebook: { url: 'https://graph.facebook.com/v3.3/me', - headers: (token) => { + headers: token => { return `Bearer ${token}`; }, propertyName: 'name', @@ -64,10 +64,10 @@ const GOOGLE_CONFIG = { provider: 'google', OAUTH_ID_KEY: 'google_client_id', OAUTH_SECRET_KEY: 'google_client_secret', - authCodeUrl: (oauthConfig) => { + authCodeUrl: oauthConfig => { return `https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=${oauthConfig.id}&scope=openid profile&state=${oauthConfig.state}`; }, - tokenUrl: (oauthConfig) => { + tokenUrl: oauthConfig => { return { url: `https://accounts.google.com/o/oauth2/token?grant_type=authorization_code&client_id=${oauthConfig.id}&client_secret=${oauthConfig.secret}`, options: { @@ -83,10 +83,10 @@ const GITHUB_CONFIG = { provider: 'github', OAUTH_ID_KEY: 'github_client_id', OAUTH_SECRET_KEY: 'github_client_secret', - authCodeUrl: (oauthConfig) => { + authCodeUrl: oauthConfig => { return `https://github.com/login/oauth/authorize?client_id=${oauthConfig.id}&state=${oauthConfig.state}`; }, - tokenUrl: (oauthConfig) => { + tokenUrl: oauthConfig => { return { url: `https://github.com/login/oauth/access_token?client_id=${oauthConfig.id}&client_secret=${oauthConfig.secret}`, options: { @@ -102,10 +102,10 @@ const FACEBOOK_CONFIG = { provider: 'facebook', OAUTH_ID_KEY: 'facebook_client_id', OAUTH_SECRET_KEY: 'facebook_client_secret', - authCodeUrl: (oauthConfig) => { + authCodeUrl: oauthConfig => { return `https://www.facebook.com/v3.3/dialog/oauth?response_type=code&client_id=${oauthConfig.id}&state=${oauthConfig.state}&display=popup`; }, - tokenUrl: (oauthConfig) => { + tokenUrl: oauthConfig => { return { url: `https://graph.facebook.com/v3.3/oauth/access_token?client_id=${oauthConfig.id}&client_secret=${oauthConfig.secret}`, options: { @@ -127,7 +127,7 @@ const FACEBOOK_CONFIG = { async function getOAuthConfig(providerConfig = {}) { if (!Object.getOwnPropertyNames(providerConfig).length) { return; - }; + } if (oauthConfig[providerConfig.provider]) { return oauthConfig[providerConfig.provider]; @@ -144,35 +144,63 @@ examples.get('/status', oAuthStatus); examples.get('/logout', logOut); examples.all('/login/google', async (request, response) => { oauthConfig.google = await getOAuthConfig(GOOGLE_CONFIG); - oauthConfig.google.authCodeUrl = GOOGLE_CONFIG.authCodeUrl(oauthConfig.google); + oauthConfig.google.authCodeUrl = GOOGLE_CONFIG.authCodeUrl( + oauthConfig.google + ); loginForConfig(request, response, oauthConfig.google, GOOGLE_CONFIG.provider); }); examples.all('/callback/google', async (request, response) => { oauthConfig.google = await getOAuthConfig(GOOGLE_CONFIG); oauthConfig.google.tokenUrl = GOOGLE_CONFIG.tokenUrl(oauthConfig.google); - callbackForConfig(request, response, oauthConfig.google, GOOGLE_CONFIG.provider); + callbackForConfig( + request, + response, + oauthConfig.google, + GOOGLE_CONFIG.provider + ); }); examples.all('/login/github', async (request, response) => { oauthConfig.github = await getOAuthConfig(GITHUB_CONFIG); - oauthConfig.github.authCodeUrl = GITHUB_CONFIG.authCodeUrl(oauthConfig.github); + oauthConfig.github.authCodeUrl = GITHUB_CONFIG.authCodeUrl( + oauthConfig.github + ); loginForConfig(request, response, oauthConfig.github, GITHUB_CONFIG.provider); }); examples.all('/callback/github', async (request, response) => { oauthConfig.github = await getOAuthConfig(GITHUB_CONFIG); oauthConfig.github.tokenUrl = GITHUB_CONFIG.tokenUrl(oauthConfig.github); - callbackForConfig(request, response, oauthConfig.github, GITHUB_CONFIG.provider); + callbackForConfig( + request, + response, + oauthConfig.github, + GITHUB_CONFIG.provider + ); }); examples.all('/login/facebook', async (request, response) => { oauthConfig.facebook = await getOAuthConfig(FACEBOOK_CONFIG); - oauthConfig.facebook.authCodeUrl = FACEBOOK_CONFIG.authCodeUrl(oauthConfig.facebook); - loginForConfig(request, response, oauthConfig.facebook, FACEBOOK_CONFIG.provider); + oauthConfig.facebook.authCodeUrl = FACEBOOK_CONFIG.authCodeUrl( + oauthConfig.facebook + ); + loginForConfig( + request, + response, + oauthConfig.facebook, + FACEBOOK_CONFIG.provider + ); }); examples.all('/callback/facebook', async (request, response) => { oauthConfig.facebook = await getOAuthConfig(FACEBOOK_CONFIG); - oauthConfig.facebook.tokenUrl = FACEBOOK_CONFIG.tokenUrl(oauthConfig.facebook); - callbackForConfig(request, response, oauthConfig.facebook, FACEBOOK_CONFIG.provider); + oauthConfig.facebook.tokenUrl = FACEBOOK_CONFIG.tokenUrl( + oauthConfig.facebook + ); + callbackForConfig( + request, + response, + oauthConfig.facebook, + FACEBOOK_CONFIG.provider + ); }); /** @@ -187,8 +215,10 @@ function loginForConfig(request, response, providerConfig, provider) { returnUrl: request.query.return || '', }); - response.redirect(`${providerConfig.authCodeUrl}&redirect_uri=${config.hosts.preview.base}` + - `/documentation/examples/personalization/oauth2_login/callback/${provider}`); + response.redirect( + `${providerConfig.authCodeUrl}&redirect_uri=${config.hosts.preview.base}` + + `/documentation/examples/personalization/oauth2_login/callback/${provider}` + ); } /** @@ -214,10 +244,12 @@ async function callbackForConfig(request, response, providerConfig, provider) { let name; // Try to get the accessToken try { - accessToken = await fetchJson(`${providerConfig.tokenUrl.url}&code=${code}&` + - `redirect_uri=${config.hosts.preview.base}` + - `/documentation/examples/personalization/oauth2_login/callback/${provider}`, - providerConfig.tokenUrl.options); + accessToken = await fetchJson( + `${providerConfig.tokenUrl.url}&code=${code}&` + + `redirect_uri=${config.hosts.preview.base}` + + `/documentation/examples/personalization/oauth2_login/callback/${provider}`, + providerConfig.tokenUrl.options + ); } catch (err) { response.clearCookie(OAUTH_COOKIE_NAME); response.redirect(generateReturnUrl(returnUrl, false)); @@ -228,7 +260,9 @@ async function callbackForConfig(request, response, providerConfig, provider) { try { name = await fetchJson(USERINFO_ENDPOINT[provider].url, { headers: { - 'Authorization': USERINFO_ENDPOINT[provider].headers(accessToken.access_token), + 'Authorization': USERINFO_ENDPOINT[provider].headers( + accessToken.access_token + ), }, }); } catch (err) { @@ -237,10 +271,14 @@ async function callbackForConfig(request, response, providerConfig, provider) { return; } - const cookie = {...{}, ...request.cookies[OAUTH_COOKIE_NAME], ...{ - loggedInWith: provider, - name: name[USERINFO_ENDPOINT[provider].propertyName], - }}; + const cookie = { + ...{}, + ...request.cookies[OAUTH_COOKIE_NAME], + ...{ + loggedInWith: provider, + name: name[USERINFO_ENDPOINT[provider].propertyName], + }, + }; response.cookie(OAUTH_COOKIE_NAME, cookie); response.redirect(generateReturnUrl(cookie.returnUrl, !!name)); } @@ -261,7 +299,9 @@ async function fetchJson(url, options = {}) { } function oAuthStatus(request, response) { - const name = request.cookies[OAUTH_COOKIE_NAME] ? request.cookies[OAUTH_COOKIE_NAME].name : ''; + const name = request.cookies[OAUTH_COOKIE_NAME] + ? request.cookies[OAUTH_COOKIE_NAME].name + : ''; response.json({ loggedIn: !!name, name, diff --git a/examples/static/samples/files/amp_url_converter.js b/examples/static/samples/files/amp_url_converter.js index 8ca01166784..39cd8d4a1e1 100644 --- a/examples/static/samples/files/amp_url_converter.js +++ b/examples/static/samples/files/amp_url_converter.js @@ -19,8 +19,9 @@ class AmpUrlConverter { this.inputView = root.getElementById('input'); this.resultView = root.getElementById('result'); this.ampUrlFactory = ampUrlFactory; - root.getElementById('input') - .addEventListener('input', this.onChange.bind(this)); + root + .getElementById('input') + .addEventListener('input', this.onChange.bind(this)); } setInput(urlString) { @@ -41,7 +42,9 @@ class AmpUrlConverter { try { const ampUrl = this.ampUrlFactory.createAmpUrl(urlString); const proxyUrl = ampUrl.getProxyUrl(); - this.showResult('' + proxyUrl + ''); + this.showResult( + '' + proxyUrl + '' + ); } catch (e) { this.showError('Invalid URL'); } @@ -79,10 +82,16 @@ function getParameterByName(name, defaultValue) { const proxyUrlPrefix = 'https://cdn.ampproject.org'; const javascriptVersion = '5'; const useCurlsEncoding = true; -const ampUrlFactory = - new AmpUrlFactory(proxyUrlPrefix, javascriptVersion, useCurlsEncoding); +const ampUrlFactory = new AmpUrlFactory( + proxyUrlPrefix, + javascriptVersion, + useCurlsEncoding +); const converter = new AmpUrlConverter(document, ampUrlFactory); -const initialUrl = getParameterByName('url', 'https://www.example.com/amp?param=1'); +const initialUrl = getParameterByName( + 'url', + 'https://www.example.com/amp?param=1' +); converter.setInput(initialUrl); diff --git a/gulpfile.js/build.js b/gulpfile.js/build.js index b9cf3d967a6..6a3939de41a 100644 --- a/gulpfile.js/build.js +++ b/gulpfile.js/build.js @@ -56,36 +56,42 @@ const TEST_CONTENT_PATH_REGEX = '^/tests/'; * @return {Promise} */ function clean() { - return del([ - project.absolute('.cache/**/*'), + return del( + [ + project.absolute('.cache/**/*'), - project.paths.DIST, - project.paths.BUILD, + project.paths.DIST, + project.paths.BUILD, - project.absolute('boilerplate/dist'), + project.absolute('boilerplate/dist'), - project.paths.CSS, - project.absolute('pages/extensions/**/*.pyc'), - project.absolute('pages/content/amp-dev/documentation/examples/documentation/**/*.html'), - project.absolute('pages/content/amp-dev/documentation/examples/previews/**/*.html'), - project.absolute('pages/icons'), - project.absolute('pages/layouts'), - project.absolute('pages/macros'), - project.absolute('pages/views'), - project.absolute('pages/.depcache.json'), - project.absolute('pages/podspec.yaml'), - - project.absolute('examples/static/samples/samples.json'), - - project.paths.GROW_BUILD_DEST, - project.paths.STATICS_DEST, - project.absolute('platform/static'), - - project.absolute('playground/dist'), - ], {'force': true}); + project.paths.CSS, + project.absolute('pages/extensions/**/*.pyc'), + project.absolute( + 'pages/content/amp-dev/documentation/examples/documentation/**/*.html' + ), + project.absolute( + 'pages/content/amp-dev/documentation/examples/previews/**/*.html' + ), + project.absolute('pages/icons'), + project.absolute('pages/layouts'), + project.absolute('pages/macros'), + project.absolute('pages/views'), + project.absolute('pages/.depcache.json'), + project.absolute('pages/podspec.yaml'), + + project.absolute('examples/static/samples/samples.json'), + + project.paths.GROW_BUILD_DEST, + project.paths.STATICS_DEST, + project.absolute('platform/static'), + + project.absolute('playground/dist'), + ], + {'force': true} + ); } - /** * Compiles all SCSS partials to CSS * @@ -97,14 +103,15 @@ function sass() { 'includePaths': project.paths.SCSS, }; - return gulp.src(`${project.paths.SCSS}/**/[^_]*.scss`) - .pipe(gulpSass(options)) - .on('error', function(e) { - console.error(e); - // eslint-disable-next-line no-invalid-this - this.emit('end'); - }) - .pipe(gulp.dest(project.paths.CSS)); + return gulp + .src(`${project.paths.SCSS}/**/[^_]*.scss`) + .pipe(gulpSass(options)) + .on('error', function(e) { + console.error(e); + // eslint-disable-next-line no-invalid-this + this.emit('end'); + }) + .pipe(gulp.dest(project.paths.CSS)); } /** @@ -113,8 +120,9 @@ function sass() { * @return {Stream} */ function templates() { - return gulp.src(`${project.paths.FRONTEND_TEMPLATES}/**/*`) - .pipe(gulp.dest(project.paths.GROW_POD)); + return gulp + .src(`${project.paths.FRONTEND_TEMPLATES}/**/*`) + .pipe(gulp.dest(project.paths.GROW_POD)); } /** @@ -123,8 +131,9 @@ function templates() { * @return {Stream} */ function icons() { - return gulp.src(`${project.paths.ICONS}/**/*`) - .pipe(gulp.dest(`${project.paths.GROW_POD}/icons`)); + return gulp + .src(`${project.paths.ICONS}/**/*`) + .pipe(gulp.dest(`${project.paths.GROW_POD}/icons`)); } /** @@ -132,7 +141,7 @@ function icons() { * @return {undefined} */ function buildFrontend(done) { - return (gulp.parallel(sass, templates, icons))(done); + return gulp.parallel(sass, templates, icons)(done); } /** @@ -151,8 +160,8 @@ async function buildPlayground() { async function buildComponentVersions() { const rules = await validatorRules.fetch(); const componentVersions = {}; - rules.extensions.forEach((e) => { - const versions = e.version.filter((v) => v !== 'latest'); + rules.extensions.forEach(e => { + const versions = e.version.filter(v => v !== 'latest'); componentVersions[e.name] = versions[versions.length - 1]; }); const content = JSON.stringify(componentVersions, null, 2); @@ -172,7 +181,6 @@ function buildBoilerplate() { }); } - /** * Builds documentation pages, preview pages and source files by parsing * the samples sources @@ -189,28 +197,29 @@ function buildSamples() { function zipTemplates() { const templateDir = path.join(project.paths.DIST, 'static/files/templates/'); mkdirp(templateDir); - return gulp.src(project.paths.TEMPLATES + '/*/*/') - .pipe(through.obj(async (file, encoding, callback) => { - const archive = archiver('zip', { - 'zlib': {'level': 9}, + return gulp.src(project.paths.TEMPLATES + '/*/*/').pipe( + through.obj(async (file, encoding, callback) => { + const archive = archiver('zip', { + 'zlib': {'level': 9}, + }); + const zipFilePath = path.join(templateDir, file.basename + '.zip'); + const zipFileStream = fs.createWriteStream(zipFilePath); + archive + .directory(file.path + '/', false) + .pipe(zipFileStream) + .on('close', () => { + signale.success(`Zipped template ${zipFilePath}`); + callback(); + }) + .on('error', e => { + signale.error(`Writing template zip ${zipFilePath} failed`, e); + callback(e); }); - const zipFilePath = path.join(templateDir, file.basename + '.zip'); - const zipFileStream = fs.createWriteStream(zipFilePath); - archive.directory(file.path + '/', false) - .pipe(zipFileStream) - .on('close', () => { - signale.success(`Zipped template ${zipFilePath}`); - callback(); - }) - .on('error', (e) => { - signale.error(`Writing template zip ${zipFilePath} failed`, e); - callback(e); - }); - archive.finalize(); - })); + archive.finalize(); + }) + ); } - /** * Runs all importers * @@ -218,10 +227,10 @@ function zipTemplates() { */ function importAll() { return Promise.all([ - (new ComponentReferenceImporter()).import(), - (new SpecImporter()).import(), - (new BlogImporter()).import(), - (new RecentGuides()).import(), + new ComponentReferenceImporter().import(), + new SpecImporter().import(), + new BlogImporter().import(), + new RecentGuides().import(), importTasks.importWorkingGroups(), // TODO: Fails on Travis with HttpError: Requires authentication // roadmapImporter.importRoadmap(), @@ -234,10 +243,9 @@ function importAll() { * @return {Promise} */ function importComponents() { - return (new ComponentReferenceImporter()).import(); + return new ComponentReferenceImporter().import(); } - /** * Builds playground and boilerplate generator, imports all remote documents, * builds samples, lints Grow pod and JavaScript. @@ -246,45 +254,48 @@ function importComponents() { */ function buildPrepare(done) { return gulp.series( - // Build playground and boilerplate that early in the flow as they are - // fairly quick to build and would be annoying to eventually fail downstream - gulp.parallel( - buildComponentVersions, - buildPlayground, - buildBoilerplate, - buildSamples, - importAll, - zipTemplates, - ), - // run reference checker after import - lint.lintAll, - // TODO: Fix working but malformatted references before reenabling - // test.lintGrow, - // eslint-disable-next-line prefer-arrow-callback - async function storeArtifcats() { - if (!travis.onTravis()) { - return; - } + // Build playground and boilerplate that early in the flow as they are + // fairly quick to build and would be annoying to eventually fail downstream + gulp.parallel( + buildComponentVersions, + buildPlayground, + buildBoilerplate, + buildSamples, + importAll, + zipTemplates + ), + // run reference checker after import + lint.lintAll, + // TODO: Fix working but malformatted references before reenabling + // test.lintGrow, + // eslint-disable-next-line prefer-arrow-callback + async function storeArtifcats() { + if (!travis.onTravis()) { + return; + } - // If on Travis store everything built so far for later stages to pick up - // Local path to the archive containing artifacts of the first stage - const SETUP_ARCHIVE = 'build/setup.tar.gz'; - // All paths that contain altered files at build setup time - const SETUP_STORED_PATHS = [ - './pages/content/', - './pages/shared/', - './dist/', - './boilerplate/dist/', - './playground/dist/', - './.cache/', - './examples/static/samples/samples.json', - ]; - - await sh('mkdir -p build'); - await sh(`tar cfj ${SETUP_ARCHIVE} ${SETUP_STORED_PATHS.join(' ')}`); - await sh(`gsutil cp ${SETUP_ARCHIVE} ` + - `${TRAVIS_GCS_PATH}${travis.build.number}/setup.tar.gz`); - })(done); + // If on Travis store everything built so far for later stages to pick up + // Local path to the archive containing artifacts of the first stage + const SETUP_ARCHIVE = 'build/setup.tar.gz'; + // All paths that contain altered files at build setup time + const SETUP_STORED_PATHS = [ + './pages/content/', + './pages/shared/', + './dist/', + './boilerplate/dist/', + './playground/dist/', + './.cache/', + './examples/static/samples/samples.json', + ]; + + await sh('mkdir -p build'); + await sh(`tar cfj ${SETUP_ARCHIVE} ${SETUP_STORED_PATHS.join(' ')}`); + await sh( + `gsutil cp ${SETUP_ARCHIVE} ` + + `${TRAVIS_GCS_PATH}${travis.build.number}/setup.tar.gz` + ); + } + )(done); } /** @@ -297,8 +308,10 @@ async function fetchArtifacts() { if (travis.onTravis() || config.options['travis-build']) { const buildNumber = config.options['travis-build'] || travis.build.number; try { - await sh(`gsutil cp -r ${TRAVIS_GCS_PATH}${buildNumber} ${project.paths.BUILD}`); - await sh('find build -type f -exec tar xf {} \;'); + await sh( + `gsutil cp -r ${TRAVIS_GCS_PATH}${buildNumber} ${project.paths.BUILD}` + ); + await sh('find build -type f -exec tar xf {} ;'); } catch (e) { // If fetching the pages fails, force exit here to make sure // especially Travis gets the correct exit code @@ -313,52 +326,62 @@ async function fetchArtifacts() { * @return {Promise} */ function buildPages(done) { - return gulp.series(fetchArtifacts, buildFrontend, - // eslint-disable-next-line prefer-arrow-callback - async function buildGrow() { - const options = {}; - if (config.isTestMode()) { - options.include_paths = TEST_CONTENT_PATH_REGEX; - options.locales = 'en'; - options.noSitemap = true; - } else if (config.isProdMode()) { - options.ignore_paths = TEST_CONTENT_PATH_REGEX; - } - config.configureGrow(options); - - try { - await grow('deploy --noconfirm --threaded'); - } catch (e) { - // If building the pages fails, force exit here to make sure - // especially Travis gets the correct exit code - process.exit(1); - } - }, minifyPages, - // eslint-disable-next-line prefer-arrow-callback - function sharedPages() { - // Copy shared pages separated from PageTransformer as they should - // not be transformed - return gulp.src(`${project.paths.GROW_BUILD_DEST}/shared/*.html`) - .pipe(gulp.dest(`${project.paths.PAGES_DEST}/shared`)); - }, - // eslint-disable-next-line prefer-arrow-callback - function sitemap() { - // Copy XML files written by Grow - return gulp.src(`${project.paths.GROW_BUILD_DEST}/**/*.xml`) - .pipe(gulp.dest(`${project.paths.PAGES_DEST}`)); - }, - // eslint-disable-next-line prefer-arrow-callback - async function storeArtifacts() { - // ... and again if on Travis store all built files for a later stage to pick up - if (travis.onTravis()) { - const archive = `build/pages-${travis.build.job}.tar.gz`; - // we need to add all folders that contain files generated by the grow process... - await sh(`tar cfj ${archive} ./dist/pages ./dist/inline-examples ` + - './dist/static/files/search-promoted-pages'); - await sh(`gsutil cp ${archive} ` + - `${TRAVIS_GCS_PATH}${travis.build.number}/pages-${travis.build.job}.tar.gz`); - } - })(done); + return gulp.series( + fetchArtifacts, + buildFrontend, + // eslint-disable-next-line prefer-arrow-callback + async function buildGrow() { + const options = {}; + if (config.isTestMode()) { + options.include_paths = TEST_CONTENT_PATH_REGEX; + options.locales = 'en'; + options.noSitemap = true; + } else if (config.isProdMode()) { + options.ignore_paths = TEST_CONTENT_PATH_REGEX; + } + config.configureGrow(options); + + try { + await grow('deploy --noconfirm --threaded'); + } catch (e) { + // If building the pages fails, force exit here to make sure + // especially Travis gets the correct exit code + process.exit(1); + } + }, + minifyPages, + // eslint-disable-next-line prefer-arrow-callback + function sharedPages() { + // Copy shared pages separated from PageTransformer as they should + // not be transformed + return gulp + .src(`${project.paths.GROW_BUILD_DEST}/shared/*.html`) + .pipe(gulp.dest(`${project.paths.PAGES_DEST}/shared`)); + }, + // eslint-disable-next-line prefer-arrow-callback + function sitemap() { + // Copy XML files written by Grow + return gulp + .src(`${project.paths.GROW_BUILD_DEST}/**/*.xml`) + .pipe(gulp.dest(`${project.paths.PAGES_DEST}`)); + }, + // eslint-disable-next-line prefer-arrow-callback + async function storeArtifacts() { + // ... and again if on Travis store all built files for a later stage to pick up + if (travis.onTravis()) { + const archive = `build/pages-${travis.build.job}.tar.gz`; + // we need to add all folders that contain files generated by the grow process... + await sh( + `tar cfj ${archive} ./dist/pages ./dist/inline-examples ` + + './dist/static/files/search-promoted-pages' + ); + await sh( + `gsutil cp ${archive} ` + + `${TRAVIS_GCS_PATH}${travis.build.number}/pages-${travis.build.job}.tar.gz` + ); + } + } + )(done); } /** @@ -377,23 +400,29 @@ function minifyPages() { }, }); - return gulp.src(`${project.paths.GROW_BUILD_DEST}/**/*.html`) - .pipe(through.obj(function(page, encoding, callback) { + return gulp + .src(`${project.paths.GROW_BUILD_DEST}/**/*.html`) + .pipe( + through.obj(function(page, encoding, callback) { let html = page.contents.toString(); // Minify the CSS const css = html.match(/(?<='; // eslint-disable-line max-len - +const AMP_STORY_CLEANER_REGEX = ['amp-story', 'amp-story-auto-ads'].map( + extension => + new RegExp( + '<\\/script>' + ) +); +const AMPHTML_BOILERPLATE = + ''; // eslint-disable-line max-len /* eslint-disable */ const BEAUTIFY_OPTIONS = { @@ -58,7 +75,7 @@ const BEAUTIFY_OPTIONS = { * - starts with an HTML comment * - spans the next tag and its children after a comment */ -module.exports.parse = function(input, filePath='') { +module.exports.parse = function(input, filePath = '') { input = beautifyHtml(input, BEAUTIFY_OPTIONS); const parsing = new DocumentParser(input.split('\n'), filePath); parsing.execute(); @@ -67,7 +84,7 @@ module.exports.parse = function(input, filePath='') { }; class DocumentParser { - constructor(lines, filePath='') { + constructor(lines, filePath = '') { this.lines = lines; this.document = new Document(); this.document.firstImage = SAMPLE_THUMBNAIL; @@ -129,9 +146,16 @@ class DocumentParser { this.document.metadata = yaml.safeLoad(this.metadata); } catch (err) { throw new Error( - 'There is an error in the YAML frontmatter in ' + - this.filePath + ' at line ' + (i + 1) + - '\n' + '"' + line + '"', err); + 'There is an error in the YAML frontmatter in ' + + this.filePath + + ' at line ' + + (i + 1) + + '\n' + + '"' + + line + + '"', + err + ); } } else if (trimmedLine.endsWith('~-->')) { this.inHint = false; @@ -226,24 +250,32 @@ class DocumentParser { } replaceAmpStoryRuntime(string) { - AMP_STORY_CLEANER_REGEX.forEach((r) => string = string.replace(r, '')); + AMP_STORY_CLEANER_REGEX.forEach(r => (string = string.replace(r, ''))); return string; } replaceAmpHtmlEmailRuntimeAddViewport(string) { return string.replace( - '', - '' + - AMPHTML_BOILERPLATE); + AMPHTML_BOILERPLATE + ); } replaceAmpAdRuntime(string) { - string = string.replace('https://amp-ads.firebaseapp.com/dist/amp-inabox.js', 'https://amp-ads.firebaseapp.com/dist/amp.js'); string = string.replace( - '', - AMPHTML_BOILERPLATE); - return string.replace('https://cdn.ampproject.org/amp4ads-v0.js', 'https://cdn.ampproject.org/v0.js'); + 'https://amp-ads.firebaseapp.com/dist/amp-inabox.js', + 'https://amp-ads.firebaseapp.com/dist/amp.js' + ); + string = string.replace( + '', + AMPHTML_BOILERPLATE + ); + return string.replace( + 'https://cdn.ampproject.org/amp4ads-v0.js', + 'https://cdn.ampproject.org/v0.js' + ); } verifySectionFilters(section) { @@ -252,7 +284,9 @@ class DocumentParser { } for (const filter of section.filters) { if (!this.document.formats().includes(filter)) { - throw new Error(`Section uses filter that's not listed in formats: ${filter}`); + throw new Error( + `Section uses filter that's not listed in formats: ${filter}` + ); } } } @@ -311,8 +345,10 @@ class DocumentParser { return ''; } let end = closingBracket; - if ((nextSpace > -1 && nextSpace < closingBracket) || - closingBracket == -1) { + if ( + (nextSpace > -1 && nextSpace < closingBracket) || + closingBracket == -1 + ) { end = nextSpace; } return string.substring(start + 1, end); @@ -370,9 +406,9 @@ class DocumentParser { } attr(line, name) { - const match = line.match(name + '=\"(.*)\"'); + const match = line.match(name + '="(.*)"'); return match ? match[1] : ''; } -}; +} module.exports.DocumentParser = DocumentParser; diff --git a/platform/lib/samples/DocumentParser.test.js b/platform/lib/samples/DocumentParser.test.js index 952c7ebefc9..32a7bb40bb8 100644 --- a/platform/lib/samples/DocumentParser.test.js +++ b/platform/lib/samples/DocumentParser.test.js @@ -56,28 +56,29 @@ describe('DocumentParser', () => { }); it('adds code', () => { - expect(parse(TAG).sections[0]) - .toEqual(newSection('', TAG + '\n', '', true, true)); + expect(parse(TAG).sections[0]).toEqual( + newSection('', TAG + '\n', '', true, true) + ); }); it('adds comments', () => { - expect(parse(COMMENT, TAG).sections) - .toEqual([ - newSection('comment\n', TAG + '\n', '', true, true), - ]); + expect(parse(COMMENT, TAG).sections).toEqual([ + newSection('comment\n', TAG + '\n', '', true, true), + ]); }); it('strips whitespace before headings', () => { - expect(parse(COMMENT_WITH_HEADING, TAG).sections[0].doc) - .toEqual('\n# heading\n\ncomment\n'); + expect(parse(COMMENT_WITH_HEADING, TAG).sections[0].doc).toEqual( + '\n# heading\n\ncomment\n' + ); }); it('adds hint', () => { const expected = newSection( - '', - `\n${TAG}\n\n`, - '', - true, - true, + '', + `\n${TAG}\n\n`, + '', + true, + true ); expected.hints = ['hint']; @@ -91,35 +92,37 @@ describe('DocumentParser', () => { describe('example spans', () => { it('element after comment', () => { - expect(parse(COMMENT, TAG, ANOTHER_TAG).sections) - .toEqual([ - newSection('comment\n', TAG + '\n', '', true, false), - newSection('', ANOTHER_TAG + '\n', '', false, true), - ]); + expect(parse(COMMENT, TAG, ANOTHER_TAG).sections).toEqual([ + newSection('comment\n', TAG + '\n', '', true, false), + newSection('', ANOTHER_TAG + '\n', '', false, true), + ]); }); it('nested elements after comment', () => { - expect(parse(COMMENT, NESTED_TAG, ANOTHER_TAG).sections) - .toEqual([ - newSection('comment\n', NESTED_TAG + '\n', '', true, false), - newSection('', ANOTHER_TAG + '\n', '', false, true), - ]); + expect(parse(COMMENT, NESTED_TAG, ANOTHER_TAG).sections).toEqual([ + newSection('comment\n', NESTED_TAG + '\n', '', true, false), + newSection('', ANOTHER_TAG + '\n', '', false, true), + ]); }); it('nested elements of same type after comment', () => { - expect(parse(COMMENT, NESTED_SAME_TAG, ANOTHER_TAG).sections) - .toEqual([ - newSection('comment\n', NESTED_SAME_TAG + '\n', '', true, false), - newSection('', ANOTHER_TAG + '\n', '', false, true), - ]); + expect(parse(COMMENT, NESTED_SAME_TAG, ANOTHER_TAG).sections).toEqual([ + newSection('comment\n', NESTED_SAME_TAG + '\n', '', true, false), + newSection('', ANOTHER_TAG + '\n', '', false, true), + ]); }); it('ignores empty lines', () => { - expect(parse(COMMENT, EMPTY_LINE, TAG, ANOTHER_TAG).sections) - .toEqual([ - newSection('comment\n', EMPTY_LINE + '\n' + TAG + '\n', '', true, false), - newSection('', ANOTHER_TAG + '\n', '', false, true), - ]); + expect(parse(COMMENT, EMPTY_LINE, TAG, ANOTHER_TAG).sections).toEqual([ + newSection( + 'comment\n', + EMPTY_LINE + '\n' + TAG + '\n', + '', + true, + false + ), + newSection('', ANOTHER_TAG + '\n', '', false, true), + ]); }); it('resets current tag after tag end', () => { const doc = parse(HEAD, COMMENT, META, LINK, HEAD_END); @@ -138,7 +141,8 @@ describe('DocumentParser', () => { }); it('marks sections in body', () => { - const sections = parse(HEAD, HEAD_END, BODY, COMMENT, TAG, BODY_END).sections; + const sections = parse(HEAD, HEAD_END, BODY, COMMENT, TAG, BODY_END) + .sections; expect(sections[0].inBody).toBe(false); expect(sections[1].inBody).toBe(true); }); @@ -173,15 +177,32 @@ describe('DocumentParser', () => { describe('adds metadata to document', () => { it('after comment', () => { - const doc = parse(COMMENT, DOCUMENT_METADATA, HEAD, TITLE, HEAD_END, BODY, COMMENT, BODY_END); + const doc = parse( + COMMENT, + DOCUMENT_METADATA, + HEAD, + TITLE, + HEAD_END, + BODY, + COMMENT, + BODY_END + ); expect(doc.metadata.experiments).toEqual(['amp-accordion']); expect(doc.sections.length).toEqual(3); }); it('invalid metadata', () => { expect(() => { - parse(COMMENT, DOCUMENT_METADATA_INVALID, HEAD, TITLE, HEAD_END, BODY, COMMENT, BODY_END); - }) - .toThrowError(/line 5/); + parse( + COMMENT, + DOCUMENT_METADATA_INVALID, + HEAD, + TITLE, + HEAD_END, + BODY, + COMMENT, + BODY_END + ); + }).toThrowError(/line 5/); }); }); @@ -249,17 +270,23 @@ describe('DocumentParser', () => { describe('parses stories', () => { it('sets isAmpStory to true', () => { - const document = parse('', '', '', ''); + const document = parse( + '', + '', + '', + '' + ); expect(document.isAmpStory).toBe(true); }); it('sets story id', () => { const document = parse( - '', - '', - '', - '', - '', - ''); + '', + '', + '', + '', + '', + '' + ); expect(document.sections[0].storyPageId).toBe('story-id'); }); }); @@ -267,7 +294,11 @@ describe('DocumentParser', () => { describe('parses runtime', () => { it('amp-story', () => { const document = parse( - '', '', '', '', '', + '', + '', + '', + '', + '' ); expect(document.isAmpStory).toBe(true); expect(document.isAmpWeb).toBe(true); diff --git a/platform/lib/samples/ElementSorting.js b/platform/lib/samples/ElementSorting.js index 7c7d1d429c0..b3b7a45d258 100644 --- a/platform/lib/samples/ElementSorting.js +++ b/platform/lib/samples/ElementSorting.js @@ -17,13 +17,18 @@ 'use strict'; // the order is important: amp-sidebar must come directly after the body -const ELEMENTS_TO_MOVE = ['amp-sidebar', 'amp-app-banner'].map((e) => matchHtmlTag(e)); +const ELEMENTS_TO_MOVE = ['amp-sidebar', 'amp-app-banner'].map(e => + matchHtmlTag(e) +); // we're using regex to match the XML tags as we might have to handle // incomplete html tags in code sections, which DOM parsers such as cheerio automatically // fix when serializing. function matchHtmlTag(tagName) { - return new RegExp('<' + tagName + '(\\s(.|\\n)*)?>(.|\\n)+<\\/' + tagName + '>', 'i'); + return new RegExp( + '<' + tagName + '(\\s(.|\\n)*)?>(.|\\n)+<\\/' + tagName + '>', + 'i' + ); } /* @@ -32,9 +37,9 @@ function matchHtmlTag(tagName) { */ class ElementSorting { apply(doc) { - ELEMENTS_TO_MOVE.forEach((matcher) => { + ELEMENTS_TO_MOVE.forEach(matcher => { const elements = this._extract(doc.sections, matcher); - elements.forEach((e) => doc.elementsAfterBody += e); + elements.forEach(e => (doc.elementsAfterBody += e)); }); } @@ -48,7 +53,10 @@ class ElementSorting { } const element = match[0]; const prefix = preview.substr(0, match.index); - const postfix = preview.substr(match.index + element.length, preview.length); + const postfix = preview.substr( + match.index + element.length, + preview.length + ); section.preview = prefix + postfix; result.push(element); } diff --git a/platform/lib/samples/ElementSorting.test.js b/platform/lib/samples/ElementSorting.test.js index 6f025044702..5d3296b137a 100644 --- a/platform/lib/samples/ElementSorting.test.js +++ b/platform/lib/samples/ElementSorting.test.js @@ -26,7 +26,8 @@ describe('ElementSorting', () => { const multiLineBody = '\n\n
'; const multiLineSidebar = '\n\n\n\n'; - const sidebarWithAttribute = ''; + const sidebarWithAttribute = + ''; beforeEach(() => { doc = new Document(); @@ -110,6 +111,5 @@ describe('ElementSorting', () => { cs.preview = string; doc.addSection(cs); return cs; - }; + } }); - diff --git a/platform/lib/samples/ExampleFile.js b/platform/lib/samples/ExampleFile.js index cbc29d55e5f..dd09a18631b 100644 --- a/platform/lib/samples/ExampleFile.js +++ b/platform/lib/samples/ExampleFile.js @@ -22,7 +22,6 @@ const path = require('path'); const PREFIX = /(^|\/)\d\d_/g; const SRC_DIR = 'src'; - /** * Encodes a string into file system compatible representation. */ @@ -31,7 +30,7 @@ function fromPath(filePath) { return null; } return new ExampleFile(filePath); -}; +} class ExampleFile { constructor(filePath) { @@ -72,17 +71,18 @@ class ExampleFile { return null; } const dir = path.dirname(this.filePath); - const files = fs.readdirSync(dir) - .sort() - .filter((file) => { - return path.extname(file) == '.html'; - }); + const files = fs + .readdirSync(dir) + .sort() + .filter(file => { + return path.extname(file) == '.html'; + }); const nextPos = files.indexOf(path.basename(this.fileName())) + 1; if (nextPos == 0 || nextPos >= files.length) { return null; } return fromPath(path.join(dir, files[nextPos])); - }; + } fileName() { return path.basename(this.filePath); @@ -143,10 +143,10 @@ class ExampleFile { clean(string) { return decodeURIComponent(string) - .toLowerCase() - .replace(/^_+/g, '') - .replace(/[!@#$%\^&*()\?']/g, '') - .replace(/_+/g, '_'); + .toLowerCase() + .replace(/^_+/g, '') + .replace(/[!@#$%\^&*()\?']/g, '') + .replace(/_+/g, '_'); } } diff --git a/platform/lib/samples/ExampleFile.test.js b/platform/lib/samples/ExampleFile.test.js index c8f266332e7..344153bbefd 100644 --- a/platform/lib/samples/ExampleFile.test.js +++ b/platform/lib/samples/ExampleFile.test.js @@ -18,19 +18,22 @@ describe('ExampleFile', () => { const ExampleFile = require('./ExampleFile'); describe('created from path', () => { - const file = ExampleFile.fromPath('src/10_Hello-world\'s/What\'s_up_100%25?.html'); + const file = ExampleFile.fromPath( + "src/10_Hello-world's/What's_up_100%25?.html" + ); it('extracts title', () => { - expect(file.title()).toBe('What\'s up 100%?'); + expect(file.title()).toBe("What's up 100%?"); }); it('use parent directory name as title if filename is index', () => { - expect(ExampleFile.fromPath('src/Samples_%26_Templates/index.html') - .title()).toBe('Samples & Templates'); + expect( + ExampleFile.fromPath('src/Samples_%26_Templates/index.html').title() + ).toBe('Samples & Templates'); }); it('extracts file name', () => { - expect(file.fileName()).toBe('What\'s_up_100%25?.html'); + expect(file.fileName()).toBe("What's_up_100%25?.html"); }); it('extracts category', () => { - expect(file.category().name).toBe('Hello-world\'s'); + expect(file.category().name).toBe("Hello-world's"); }); }); @@ -44,35 +47,43 @@ describe('ExampleFile', () => { describe('nextFile', () => { const TEST_DIR = __dirname + '/FileNameTestFiles/'; it('returns next file in alphabetical order', () => { - expect(ExampleFile.fromPath(TEST_DIR + 'a.html').nextFile().filePath) - .toEqual(TEST_DIR + 'b.html'); + expect( + ExampleFile.fromPath(TEST_DIR + 'a.html').nextFile().filePath + ).toEqual(TEST_DIR + 'b.html'); }); it('returns undefined when the file is the last one in alphabetical order', () => { - expect(ExampleFile.fromPath(TEST_DIR + 'b.html').nextFile()) - .toEqual(null); + expect(ExampleFile.fromPath(TEST_DIR + 'b.html').nextFile()).toEqual( + null + ); }); it('returns undefined when the file does not exist', () => { - expect(ExampleFile.fromPath(TEST_DIR + 'notExistentFile.html').nextFile()) - .toEqual(null); + expect( + ExampleFile.fromPath(TEST_DIR + 'notExistentFile.html').nextFile() + ).toEqual(null); }); it('returns undefined when the file has no category', () => { - expect(ExampleFile.fromPath('src/amp-form-error.html').nextFile()) - .toEqual(null); + expect( + ExampleFile.fromPath('src/amp-form-error.html').nextFile() + ).toEqual(null); }); }); describe('section', () => { it('returns parent dir if section', () => { - expect(ExampleFile.fromPath('src/amp-ads/10_introduction/hello.html').section().path) - .toEqual('/amp-ads'); + expect( + ExampleFile.fromPath('src/amp-ads/10_introduction/hello.html').section() + .path + ).toEqual('/amp-ads'); }); it('returns root path if no section', () => { - expect(ExampleFile.fromPath('src/10_introduction/hello.html').section().path) - .toEqual('/'); + expect( + ExampleFile.fromPath('src/10_introduction/hello.html').section().path + ).toEqual('/'); }); it('returns root path if no category', () => { - expect(ExampleFile.fromPath('src/hello.html').section().path) - .toEqual('/'); + expect(ExampleFile.fromPath('src/hello.html').section().path).toEqual( + '/' + ); }); }); }); diff --git a/platform/lib/samples/FileName.js b/platform/lib/samples/FileName.js index b785bd57e13..7bc2a8a2c3d 100644 --- a/platform/lib/samples/FileName.js +++ b/platform/lib/samples/FileName.js @@ -55,7 +55,7 @@ module.exports.toString = function(file) { string = stripFileExtension(string); string = string.replace(/_/g, ' '); string = decodeURIComponent(string); - string = string.replace(/%27/g, '\''); + string = string.replace(/%27/g, "'"); return string; }; diff --git a/platform/lib/samples/FileName.test.js b/platform/lib/samples/FileName.test.js index 3149710e6ec..8d4ab82910b 100644 --- a/platform/lib/samples/FileName.test.js +++ b/platform/lib/samples/FileName.test.js @@ -19,49 +19,45 @@ describe('FileName', () => { describe('fromString', () => { it('returns empty string for undefined', () => { - expect(FileName.fromString(null)) - .toEqual(''); + expect(FileName.fromString(null)).toEqual(''); }); it('appends .html', () => { - expect(FileName.fromString('file')) - .toEqual('file.html'); + expect(FileName.fromString('file')).toEqual('file.html'); }); it('replaces whitespace with _', () => { - expect(FileName.fromString('A String with whitespace')) - .toEqual('A_String_with_whitespace.html'); + expect(FileName.fromString('A String with whitespace')).toEqual( + 'A_String_with_whitespace.html' + ); }); it('URI encodes other chars', () => { - expect(FileName.fromString('What\'s possible with X?')) - .toEqual('What%27s_possible_with_X%3F.html'); + expect(FileName.fromString("What's possible with X?")).toEqual( + 'What%27s_possible_with_X%3F.html' + ); }); it('returns empty string when the file is undefined', () => { - expect(FileName.fromString(undefined)) - .toEqual(''); + expect(FileName.fromString(undefined)).toEqual(''); }); it('Adds path', () => { - expect(FileName.fromString('hello', 'world')) - .toEqual('hello/world.html'); + expect(FileName.fromString('hello', 'world')).toEqual('hello/world.html'); }); }); describe('toString', () => { it('removes file extension', () => { - expect(FileName.toString('file.txt')) - .toEqual('file'); + expect(FileName.toString('file.txt')).toEqual('file'); }); it('replaces _ with space', () => { - expect(FileName.toString('A_String_with_whitespace')) - .toEqual('A String with whitespace'); + expect(FileName.toString('A_String_with_whitespace')).toEqual( + 'A String with whitespace' + ); }); it('URI encodes other chars', () => { - expect(FileName.toString('What%27s_possible_with_X%3F')) - .toEqual('What\'s possible with X?'); + expect(FileName.toString('What%27s_possible_with_X%3F')).toEqual( + "What's possible with X?" + ); }); it('returns empty string when the file is undefined', () => { - expect(FileName.toString(undefined)) - .toEqual(''); + expect(FileName.toString(undefined)).toEqual(''); }); }); }); - - diff --git a/platform/lib/samples/index.js b/platform/lib/samples/index.js index 00148240c0e..a1fb6eed8a1 100644 --- a/platform/lib/samples/index.js +++ b/platform/lib/samples/index.js @@ -39,7 +39,9 @@ module.exports.parseSample = async (filePath, config, contents) => { doc.title = exampleFile.title(); } else if (doc.title !== exampleFile.title()) { console.warn( - `${filePath} has invalid title: "${exampleFile.title()}" vs "${doc.title}"`, + `${filePath} has invalid title: "${exampleFile.title()}" vs "${ + doc.title + }"` ); } exampleFile.source = contents.replace(/\/gms, '').trim(); diff --git a/platform/lib/templates/index.js b/platform/lib/templates/index.js index e808b15b8a4..629c01b1ba0 100644 --- a/platform/lib/templates/index.js +++ b/platform/lib/templates/index.js @@ -32,7 +32,7 @@ let templates = null; * @param {expressjs.Request} request * @return {Object} */ -function createRequestContext(request={'query': {}}, context={}) { +function createRequestContext(request = {'query': {}}, context = {}) { // Store the initially requested format to be able // to match user request against available formats later context.requestedFormat = request.query.format; @@ -78,10 +78,14 @@ class Templates { variableEnd: '=]', commentStart: '[#', commentEnd: '#]', - }}); + }, + }); // Add extension to determine default document format at runtime - this.nunjucksEnv_.addExtension('SupportedFormatsExtension', new SupportedFormatsExtension()); + this.nunjucksEnv_.addExtension( + 'SupportedFormatsExtension', + new SupportedFormatsExtension() + ); // One locale has ~860 pages with each weighing ~92KB. The cache therefore // maxes out at ~224MB to be safe diff --git a/platform/lib/tools/componentReferenceLinker.js b/platform/lib/tools/componentReferenceLinker.js index a53f13addcb..4a4a5c2af47 100644 --- a/platform/lib/tools/componentReferenceLinker.js +++ b/platform/lib/tools/componentReferenceLinker.js @@ -26,8 +26,11 @@ const POD_BASE_PATH = path.join(__dirname, '../../../pages/'); // Which documents to check for broken references // eslint-disable-next-line max-len -const PAGES_SRC = POD_BASE_PATH + 'content/amp-dev/documentation/guides-and-tutorials/develop/interactivity/remote-data.md'; -const COMPONENTS_SRC = POD_BASE_PATH + 'content/amp-dev/documentation/components/'; +const PAGES_SRC = + POD_BASE_PATH + + 'content/amp-dev/documentation/guides-and-tutorials/develop/interactivity/remote-data.md'; +const COMPONENTS_SRC = + POD_BASE_PATH + 'content/amp-dev/documentation/components/'; /** * Walks over documents inside the Grow pod and looks for broken links either @@ -49,10 +52,12 @@ class ComponentReferenceLinker { return new Promise((resolve, reject) => { let stream = gulp.src(PAGES_SRC, {'read': true, 'base': './'}); - stream = stream.pipe(through.obj((doc, encoding, callback) => { - stream.push(this._link(doc)); - callback(); - })); + stream = stream.pipe( + through.obj((doc, encoding, callback) => { + stream.push(this._link(doc)); + callback(); + }) + ); stream.pipe(gulp.dest('./')); stream.on('end', () => { @@ -68,17 +73,24 @@ class ComponentReferenceLinker { // Cut out code Examples to avoid errors in replacement process // eslint-disable-next-line max-len - const codeExamples = content.match(/(<(amp-[^\s]+)(?:\s[^>]*)?>([^`]*)?<\/\2>|```html(.*?)*?```|```css(.*?)*?```|Preview:(.*?)*?<\/amp-\w*(-\w*)*\>|<\/script>||\[sourcecode:\w*](.*?)*?\[\/sourcecode]|
(.*?)\/pre>)/gms);
+    const codeExamples = content.match(
+      /(<(amp-[^\s]+)(?:\s[^>]*)?>([^`]*)?<\/\2>|```html(.*?)*?```|```css(.*?)*?```|Preview:(.*?)*?<\/amp-\w*(-\w*)*\>|<\/script>||\[sourcecode:\w*](.*?)*?\[\/sourcecode]|
(.*?)\/pre>)/gms
+    );
     if (codeExamples !== null) {
       for (let i = 0; i < codeExamples.length; i++) {
         const codeExample = codeExamples[i];
-        content = content.replace(codeExample, this._createCodePlaceholder(codeExample));
+        content = content.replace(
+          codeExample,
+          this._createCodePlaceholder(codeExample)
+        );
       }
     }
 
     // Check html tables for amp-component names and replace with html placeholder
     const tableExamples = [
-      content.match(/\.*?amp-\w*?(-\w*?)*?.*?<\/a>/gm),
+      content.match(
+        /\.*?amp-\w*?(-\w*?)*?.*?<\/a>/gm
+      ),
       content.match(/\.*?amp-\w*?(-\w*)*.*?\/code>/gm),
     ];
     for (let i = 0; i < tableExamples.length; i++) {
@@ -93,16 +105,26 @@ class ComponentReferenceLinker {
           }
         }
       }
-    };
+    }
 
     // Check document for amp-components and replace with md placeholder
     /* eslint-disable max-len */
     const cases = [
-      content.match(/\[amp-\w*(-\w*)*\]\(\/docs\/reference\/components\/\w*-\w*(-\w*)*\.html\)/gm),
-      content.match(/\[`amp-\w*(-\w*)*\`]\(\/docs\/reference\/components\/\w*-\w*(-\w*)*\.html\)/gm),
-      content.match(/\[``]\(\/docs\/reference\/components\/\w*-\w*(-\w*)*\.html(.*)?\)/gm),
-      content.match(/\[amp-\w*(-\w*)*]\(https:\/\/www.ampproject.org\/docs\/reference\/components\/\w*-\w*(-\w*)*\)/gm),
-      content.match(/\[\`amp-\w*(-\w*)*\`]\(https:\/\/www.ampproject.org\/docs\/reference\/components\/\w*-\w*(-\w*)*\)/gm),
+      content.match(
+        /\[amp-\w*(-\w*)*\]\(\/docs\/reference\/components\/\w*-\w*(-\w*)*\.html\)/gm
+      ),
+      content.match(
+        /\[`amp-\w*(-\w*)*\`]\(\/docs\/reference\/components\/\w*-\w*(-\w*)*\.html\)/gm
+      ),
+      content.match(
+        /\[``]\(\/docs\/reference\/components\/\w*-\w*(-\w*)*\.html(.*)?\)/gm
+      ),
+      content.match(
+        /\[amp-\w*(-\w*)*]\(https:\/\/www.ampproject.org\/docs\/reference\/components\/\w*-\w*(-\w*)*\)/gm
+      ),
+      content.match(
+        /\[\`amp-\w*(-\w*)*\`]\(https:\/\/www.ampproject.org\/docs\/reference\/components\/\w*-\w*(-\w*)*\)/gm
+      ),
       content.match(/\[\`amp-\w*(-\w*)*\`]\(https:\/\/github.*\.md\)/gm),
       content.match(/\[amp-\w*(-\w*)*.*]\(.*\)/gm),
       content.match(/\[(.*)?amp-\w*(-\w*)*.*]\(.*\)/gm),
@@ -120,17 +142,29 @@ class ComponentReferenceLinker {
 
         // Continue when component name is found in existing path
         // eslint-disable-next-line max-len
-        if (result.slice(-1) === '/' || result.slice(-1) === '.' || result.slice(-1) === '>') {
+        if (
+          result.slice(-1) === '/' ||
+          result.slice(-1) === '.' ||
+          result.slice(-1) === '>'
+        ) {
           continue;
         } else {
           const component = result.match(/amp-\w*(-\w*)*/g)[0];
-          const linkDescription = result.match(/(?<=\[)(.* )?amp-\w*(-\w*)*( .*)?(?=])/g);
+          const linkDescription = result.match(
+            /(?<=\[)(.* )?amp-\w*(-\w*)*( .*)?(?=])/g
+          );
           // eslint-disable-next-line max-len
-          const description = ((linkDescription !== null) ? linkDescription[0].replace(component, `\`${component}\``) : `\`${component}\``);
+          const description =
+            linkDescription !== null
+              ? linkDescription[0].replace(component, `\`${component}\``)
+              : `\`${component}\``;
           if (this._componentExist(component) === true) {
             while (content.includes(result)) {
               // eslint-disable-next-line max-len
-              const placeholder = ((i === cases.length-1) ? this._createPlaceholder(component, description) + ' ' : this._createPlaceholder(component, description));
+              const placeholder =
+                i === cases.length - 1
+                  ? this._createPlaceholder(component, description) + ' '
+                  : this._createPlaceholder(component, description);
               content = content.replace(result, placeholder);
             }
           }
@@ -146,12 +180,18 @@ class ComponentReferenceLinker {
     }
     for (const placeholder of Object.keys(this._codePlaceholders)) {
       while (content.includes(codePlaceholder)) {
-        content = content.replace(placeholder, this._codePlaceholders[placeholder]);
+        content = content.replace(
+          placeholder,
+          this._codePlaceholders[placeholder]
+        );
       }
     }
     for (const placeholder of Object.keys(this._tablePlaceholders)) {
       while (content.includes(placeholder)) {
-        content = content.replace(placeholder, this._tablePlaceholders[placeholder]);
+        content = content.replace(
+          placeholder,
+          this._tablePlaceholders[placeholder]
+        );
       }
     }
 
@@ -166,9 +206,12 @@ class ComponentReferenceLinker {
   }
 
   _createPlaceholder(component, description) {
-    const placeholder =``;
+    const placeholder = ``;
     if (!this._placeholders[placeholder]) {
-      this._placeholders[placeholder] = this._componentPath(component, description);
+      this._placeholders[placeholder] = this._componentPath(
+        component,
+        description
+      );
     }
     return placeholder;
   }
@@ -182,9 +225,11 @@ class ComponentReferenceLinker {
   }
 
   _createTablePlaceholder(component) {
-    const placeholder =``;
+    const placeholder = ``;
     if (!this._tablePlaceholders[placeholder]) {
-      this._tablePlaceholders[placeholder] = this._tableComponentPath(component);
+      this._tablePlaceholders[placeholder] = this._tableComponentPath(
+        component
+      );
     }
     return placeholder;
   }
diff --git a/platform/lib/tools/generalReferenceLinker.js b/platform/lib/tools/generalReferenceLinker.js
index 697a7718369..84ace41e73d 100644
--- a/platform/lib/tools/generalReferenceLinker.js
+++ b/platform/lib/tools/generalReferenceLinker.js
@@ -26,9 +26,12 @@ const POD_BASE_PATH = path.join(__dirname, '../../../pages/');
 
 // Which documents to check for broken references
 /* eslint-disable max-len */
-const PAGES_SRC = POD_BASE_PATH + 'content/amp-dev/documentation/guides-and-tutorials/**/*.md';
-const COMPONENTS_SRC = POD_BASE_PATH + 'content/amp-dev/documentation/components';
-const TUTORIAL_SRC = POD_BASE_PATH + 'content/amp-dev/documentation/guides-and-tutorials';
+const PAGES_SRC =
+  POD_BASE_PATH + 'content/amp-dev/documentation/guides-and-tutorials/**/*.md';
+const COMPONENTS_SRC =
+  POD_BASE_PATH + 'content/amp-dev/documentation/components';
+const TUTORIAL_SRC =
+  POD_BASE_PATH + 'content/amp-dev/documentation/guides-and-tutorials';
 const EXAMPLE_SRC = POD_BASE_PATH + 'content/amp-dev/documentation/examples';
 /* eslint-enable max-len */
 
@@ -49,34 +52,44 @@ class ComponentReferenceLinker {
   async start() {
     return new Promise((resolve, reject) => {
       let stream = gulp.src(PAGES_SRC, {'read': true, 'base': './'});
-      stream = stream.pipe(through.obj((doc, encoding, callback) => {
-        stream.push(this._check(doc));
-        callback();
-      }));
+      stream = stream.pipe(
+        through.obj((doc, encoding, callback) => {
+          stream.push(this._check(doc));
+          callback();
+        })
+      );
 
       stream.pipe(gulp.dest('./'));
       stream.on('end', () => {
         // Write missing references in file
-        let referenceText = 'Missing references: ' + this._missingReferences.length;
+        let referenceText =
+          'Missing references: ' + this._missingReferences.length;
         for (const reference of this._missingReferences) {
           referenceText = referenceText.concat(
-              '\n\n',
-              reference.document,
-              '\n-> ',
-              reference.result,
-              '\n-> Type: ',
-              reference.link.type,
-              ' - Name: ',
-              reference.link.name,
+            '\n\n',
+            reference.document,
+            '\n-> ',
+            reference.result,
+            '\n-> Type: ',
+            reference.link.type,
+            ' - Name: ',
+            reference.link.name
           );
         }
-        fs.writeFile(POD_BASE_PATH + 'content/missing.txt', referenceText, (err) => {
-          if (err) throw err;
-        });
+        fs.writeFile(
+          POD_BASE_PATH + 'content/missing.txt',
+          referenceText,
+          err => {
+            if (err) throw err;
+          }
+        );
 
         this._log.complete('Linked all component references!');
-        this._log.complete('Saved ', this._missingReferences.length,
-            ' missing references in content/missing.txt');
+        this._log.complete(
+          'Saved ',
+          this._missingReferences.length,
+          ' missing references in content/missing.txt'
+        );
         resolve();
       });
     });
@@ -92,7 +105,9 @@ class ComponentReferenceLinker {
     console.log();
     this._log.await('Inspecting doc:', doc.relative);
 
-    const results = Array.from(new Set(content.match(/\[[^\]]*?]\((?!\{)(.*?)\)(?=(\s|\.|\,))/gm)));
+    const results = Array.from(
+      new Set(content.match(/\[[^\]]*?]\((?!\{)(.*?)\)(?=(\s|\.|\,))/gm))
+    );
     for (const result of results) {
       const link = this._linkType(result);
       if (link.type == null) {
@@ -101,19 +116,49 @@ class ComponentReferenceLinker {
 
       switch (link.type) {
         case 'example':
-          content = this._handleFile(doc.relative, content, result, link, EXAMPLE_SRC);
+          content = this._handleFile(
+            doc.relative,
+            content,
+            result,
+            link,
+            EXAMPLE_SRC
+          );
           break;
         case 'componentOverview':
-          content = this._handleFile(doc.relative, content, result, link, COMPONENTS_SRC);
+          content = this._handleFile(
+            doc.relative,
+            content,
+            result,
+            link,
+            COMPONENTS_SRC
+          );
           break;
         case 'exampleOverview':
-          content = this._handleFile(doc.relative, content, result, link, EXAMPLE_SRC);
+          content = this._handleFile(
+            doc.relative,
+            content,
+            result,
+            link,
+            EXAMPLE_SRC
+          );
           break;
         case 'component':
-          content = this._handleFile(doc.relative, content, result, link, COMPONENTS_SRC);
+          content = this._handleFile(
+            doc.relative,
+            content,
+            result,
+            link,
+            COMPONENTS_SRC
+          );
           break;
         case 'tutorial':
-          content = this._handleFile(doc.relative, content, result, link, TUTORIAL_SRC);
+          content = this._handleFile(
+            doc.relative,
+            content,
+            result,
+            link,
+            TUTORIAL_SRC
+          );
           break;
         default:
           this._log.error('--> Type undifined', result);
@@ -132,33 +177,66 @@ class ComponentReferenceLinker {
 
   _linkType(result) {
     const linkText = this._linkText(result);
-    const isValid = (str) => /(\(\/)|(ampbyexample\.com)/.test(str);
+    const isValid = str => /(\(\/)|(ampbyexample\.com)/.test(str);
     if (!isValid(result)) {
       return {};
     }
 
     /* eslint-disable max-len */
-    const exampleMatch = result.match(/ampbyexample\.com\/((?:[^\/]+\/)*)([^\/]+)\/\)/m);
+    const exampleMatch = result.match(
+      /ampbyexample\.com\/((?:[^\/]+\/)*)([^\/]+)\/\)/m
+    );
     // eslint-disable-next-line no-unused-vars
     const [example, path, exampleName] = exampleMatch || [];
     if (exampleName) {
-      return {'type': 'example', 'name': exampleName, 'text': linkText.text, 'id': linkText.id};
+      return {
+        'type': 'example',
+        'name': exampleName,
+        'text': linkText.text,
+        'id': linkText.id,
+      };
     }
-    const componentOverview = result.match(/\[.*?\]\((\/\w+)?\/docs\/reference\/components\.html(#\w+)?\)/gm);
+    const componentOverview = result.match(
+      /\[.*?\]\((\/\w+)?\/docs\/reference\/components\.html(#\w+)?\)/gm
+    );
     if (componentOverview) {
-      return {'type': 'componentOverview', 'name': 'index', 'text': linkText.text, 'id': linkText.id};
+      return {
+        'type': 'componentOverview',
+        'name': 'index',
+        'text': linkText.text,
+        'id': linkText.id,
+      };
     }
-    const exampleOverview = result.match(/\[.*?\]\((\/\w+)?https:\/\/ampbyexample\.com(#\w+)?\)/gm);
+    const exampleOverview = result.match(
+      /\[.*?\]\((\/\w+)?https:\/\/ampbyexample\.com(#\w+)?\)/gm
+    );
     if (exampleOverview) {
-      return {'type': 'exampleOverview', 'name': 'index', 'text': linkText.text, 'id': linkText.id};
+      return {
+        'type': 'exampleOverview',
+        'name': 'index',
+        'text': linkText.text,
+        'id': linkText.id,
+      };
     }
     const componentName = result.match(/amp-\w*(-\w*)*/m);
     if (componentName) {
-      return {'type': 'component', 'name': componentName[0], 'text': linkText.text, 'id': linkText.id};
+      return {
+        'type': 'component',
+        'name': componentName[0],
+        'text': linkText.text,
+        'id': linkText.id,
+      };
     }
-    const tutorialName = result.match(/(?!\](.*?)\/docs\/\w+\/)(\w+-)*\w+(?=\.html)/m);
+    const tutorialName = result.match(
+      /(?!\](.*?)\/docs\/\w+\/)(\w+-)*\w+(?=\.html)/m
+    );
     if (tutorialName) {
-      return {'type': 'tutorial', 'name': tutorialName[0], 'text': linkText.text, 'id': linkText.id};
+      return {
+        'type': 'tutorial',
+        'name': tutorialName[0],
+        'text': linkText.text,
+        'id': linkText.id,
+      };
     }
     /* eslint-enable max-len */
     return {};
@@ -166,7 +244,7 @@ class ComponentReferenceLinker {
 
   _linkText(result) {
     const section = result.match(/#\w*(-.*)?(?=\))/gm);
-    const sectionId = ((section !== null) ? section[0].replace(/\s/g, '') : '');
+    const sectionId = section !== null ? section[0].replace(/\s/g, '') : '';
     const text = result.match(/\[(.*?)]/gm);
     if (text !== null) {
       return {'text': text, 'id': sectionId};
@@ -177,12 +255,22 @@ class ComponentReferenceLinker {
   _handleFile(docPath, content, result, link, src) {
     if (this._fileExistsAtPath(link, src) !== undefined) {
       while (content.includes(result)) {
-        const placeholder = this._createPlaceholder(result, this._getReferencePath(link, path));
-        return content.replace(result, this._createPlaceholder(result, placeholder));
+        const placeholder = this._createPlaceholder(
+          result,
+          this._getReferencePath(link, path)
+        );
+        return content.replace(
+          result,
+          this._createPlaceholder(result, placeholder)
+        );
       }
       this._log.start('--> Valid ' + link.type + ' replaced:', result);
     } else {
-      this._missingReferences.push({'document': docPath, 'result': result, 'link': link});
+      this._missingReferences.push({
+        'document': docPath,
+        'result': result,
+        'link': link,
+      });
       this._log.error('--> No valid ' + link.type + ' found in:', result);
     }
     return content;
@@ -195,7 +283,7 @@ class ComponentReferenceLinker {
   }
 
   _createPlaceholder(result, newReference) {
-    const placeholder =``;
+    const placeholder = ``;
     if (!this._placeholders[placeholder]) {
       this._placeholders[placeholder] = newReference;
     }
@@ -221,9 +309,15 @@ class ComponentReferenceLinker {
     const existingFiles = this._getFiles(src);
     for (const filePath of existingFiles) {
       // eslint-disable-next-line max-len
-      if (filePath.toLowerCase().includes(`/${link.name}.html`) || filePath.toLowerCase().includes(`/${link.name}.md`)) {
+      if (
+        filePath.toLowerCase().includes(`/${link.name}.html`) ||
+        filePath.toLowerCase().includes(`/${link.name}.md`)
+      ) {
         // eslint-disable-next-line max-len
-        path = filePath.replace(POD_BASE_PATH + 'content/amp-dev/documentation/', '');
+        path = filePath.replace(
+          POD_BASE_PATH + 'content/amp-dev/documentation/',
+          ''
+        );
       }
     }
     return path;
diff --git a/platform/lib/tools/growReferenceChecker.js b/platform/lib/tools/growReferenceChecker.js
index 0c52723d968..30e991222a3 100644
--- a/platform/lib/tools/growReferenceChecker.js
+++ b/platform/lib/tools/growReferenceChecker.js
@@ -33,8 +33,8 @@ const PAGES_BASE_PATH = POD_BASE_PATH + 'content/amp-dev';
 // The pattern to find links in markdown and html
 // It also matches source code blocks to skip these
 const REFERENCE_PATTERN = new RegExp(
-    // skip sourcecode block in markdown:
-    /^```[\s\S]*?```/.source +
+  // skip sourcecode block in markdown:
+  /^```[\s\S]*?```/.source +
     '|' +
     // skip sourcecode tag in markdown
     /\[sourcecode[^\]]*\][\s\S]*?\[\/sourcecode\]/.source +
@@ -46,28 +46,30 @@ const REFERENCE_PATTERN = new RegExp(
     /\[[^\]]+\]\(([^:\)\{?#]*)(?:\?[^#\)]*)?(#[^\)]*)?\)/.source +
     '|' +
     // find {{g.doc('link')}} links:
-    /g.doc\('(.*?)'/.source
-    , 'gm');
+    /g.doc\('(.*?)'/.source,
+  'gm'
+);
 /* eslint-disable max-len */
 // Contains manual hints for double filenames etc.
 const LOOKUP_TABLE = {
-  '/content/amp-dev/documentation/guides-and-tutorials/learn/validate.md': '/content/amp-dev/documentation/guides-and-tutorials/learn/validation-workflow/index.md',
+  '/content/amp-dev/documentation/guides-and-tutorials/learn/validate.md':
+    '/content/amp-dev/documentation/guides-and-tutorials/learn/validation-workflow/index.md',
   '/content/amp-dev/documentation/guides-and-tutorials/learn/how_cached.md':
-  '/content/amp-dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/index.md',
+    '/content/amp-dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/index.md',
   '/content/amp-dev/documentation/guides-and-tutorials/develop/media_iframes_3p/amp_replacements.md':
-  '/content/amp-dev/documentation/guides-and-tutorials/develop/media_iframes_3p/index.md',
+    '/content/amp-dev/documentation/guides-and-tutorials/develop/media_iframes_3p/index.md',
   '/content/amp-dev/documentation/guides-and-tutorials/integrate/pwa-amp/index.md':
-  '/content/amp-dev/documentation/guides-and-tutorials/integrate/amp-in-pwa.md',
-  '/content/amp-dev/documentation/guides-and-tutorials/learn/spec/index.md': '/content/amp-dev/documentation/guides-and-tutorials/learn/spec/amphtml.md',
+    '/content/amp-dev/documentation/guides-and-tutorials/integrate/amp-in-pwa.md',
+  '/content/amp-dev/documentation/guides-and-tutorials/learn/spec/index.md':
+    '/content/amp-dev/documentation/guides-and-tutorials/learn/spec/amphtml.md',
 };
 /* eslint-enable max-len */
 // The following paths are skipped when checked for existance
-const IGNORED_PATH_PATTERNS =
-  /\/content\/amp-dev\/documentation\/components\/reference\/.*?|\/boilerplate/g;
+const IGNORED_PATH_PATTERNS = /\/content\/amp-dev\/documentation\/components\/reference\/.*?|\/boilerplate/g;
 
 // The list of imported docs. Here we do not check anchors.
-const IMPORTED_DOCS = require(__dirname + '/../../config/imports/spec.json')
-    .map((spec) => '/content/amp-dev/' + spec.to);
+const IMPORTED_DOCS = require(__dirname +
+  '/../../config/imports/spec.json').map(spec => '/content/amp-dev/' + spec.to);
 
 /**
  * Walks over documents inside the Grow pod and looks for broken links either
@@ -95,16 +97,20 @@ class GrowReferenceChecker {
   }
 
   start() {
-    this._log.start(`Inspecting documents in ${PAGES_SRC} for broken references ...`);
+    this._log.start(
+      `Inspecting documents in ${PAGES_SRC} for broken references ...`
+    );
 
     return new Promise(async (resolve, reject) => {
       await this._readAnchors();
 
       let stream = gulp.src(PAGES_SRC, {'read': true, 'base': './'});
 
-      stream = stream.pipe(through.obj((doc, encoding, callback) => {
-        callback(null, this._check(doc));
-      }));
+      stream = stream.pipe(
+        through.obj((doc, encoding, callback) => {
+          callback(null, this._check(doc));
+        })
+      );
 
       stream = stream.pipe(gulp.dest('./'));
 
@@ -113,22 +119,28 @@ class GrowReferenceChecker {
 
         if (this._brokenReferencesCount > 0) {
           this._log.complete('Finished automatic fixing.');
-          this._log.complete(`A total of ${this._brokenReferencesCount} links had ` +
-            `errors. ${this._unfindableDocuments.length +
-            Object.keys(this._multipleMatches).length} still have.`);
+          this._log.complete(
+            `A total of ${this._brokenReferencesCount} links had ` +
+              `errors. ${this._unfindableDocuments.length +
+                Object.keys(this._multipleMatches).length} still have.`
+          );
         }
 
-        if (Object.keys(this._multipleMatches).length == 0 &&
-            this._unfindableDocuments.length == 0 &&
-            this._wrongAnchorCount == 0) {
+        if (
+          Object.keys(this._multipleMatches).length == 0 &&
+          this._unfindableDocuments.length == 0 &&
+          this._wrongAnchorCount == 0
+        ) {
           this._log.success('All references intact!');
           resolve();
           return;
         }
 
         if (this._unfindableDocuments.length) {
-          this._log.info(`Could not automatically fix ${this._unfindableDocuments.length} ` +
-            'as there wasn\'t any document with a matching basename:');
+          this._log.info(
+            `Could not automatically fix ${this._unfindableDocuments.length} ` +
+              "as there wasn't any document with a matching basename:"
+          );
           for (const documentPath of this._unfindableDocuments) {
             this._log.pending(`- ${documentPath}`);
           }
@@ -138,22 +150,38 @@ class GrowReferenceChecker {
 
         const multipleMatchesCount = Object.keys(this._multipleMatches).length;
         if (multipleMatchesCount !== 0) {
-          this._log.info(`Encountered multiple possible matches for ${multipleMatchesCount} ` +
-          'documents:');
+          this._log.info(
+            `Encountered multiple possible matches for ${multipleMatchesCount} ` +
+              'documents:'
+          );
           for (const documentPath in this._multipleMatches) {
-            if (Object.prototype.hasOwnProperty.call(this._multipleMatches, documentPath)) {
+            if (
+              Object.prototype.hasOwnProperty.call(
+                this._multipleMatches,
+                documentPath
+              )
+            ) {
               this._log.pending(`${documentPath}`);
               for (const possibleMatch of this._multipleMatches[documentPath]) {
-                this._log.pending(`-- ${possibleMatch.replace(POD_BASE_PATH, '/')}`);
+                this._log.pending(
+                  `-- ${possibleMatch.replace(POD_BASE_PATH, '/')}`
+                );
               }
             }
           }
         }
 
-        if (this._unfindableDocuments.length > 0 || multipleMatchesCount > 0 ||
-            this._wrongAnchorCount > 0) {
-          reject(new Error(`${this._unfindableDocuments.length + multipleMatchesCount} ` +
-              `broken links and ${this._wrongAnchorCount} wrong anchors found`));
+        if (
+          this._unfindableDocuments.length > 0 ||
+          multipleMatchesCount > 0 ||
+          this._wrongAnchorCount > 0
+        ) {
+          reject(
+            new Error(
+              `${this._unfindableDocuments.length + multipleMatchesCount} ` +
+                `broken links and ${this._wrongAnchorCount} wrong anchors found`
+            )
+          );
         } else {
           resolve();
         }
@@ -165,15 +193,19 @@ class GrowReferenceChecker {
     return new Promise((resolve, reject) => {
       // we skip html files, since they sometimes use imports of other documents
       // where we cannot resolve the anchors
-      let stream = gulp.src([PAGES_SRC, `!${POD_BASE_PATH}/**/*.html`],
-          {'read': true, 'base': './'});
+      let stream = gulp.src([PAGES_SRC, `!${POD_BASE_PATH}/**/*.html`], {
+        'read': true,
+        'base': './',
+      });
       stream.on('end', () => {
         resolve();
       });
-      stream = stream.pipe(through.obj((doc, encoding, callback) => {
-        this._readAnchorsForDoc(doc, callback);
-        callback();
-      }));
+      stream = stream.pipe(
+        through.obj((doc, encoding, callback) => {
+          this._readAnchorsForDoc(doc, callback);
+          callback();
+        })
+      );
     });
   }
 
@@ -182,13 +214,13 @@ class GrowReferenceChecker {
     const content = doc.contents.toString();
 
     const TITLE_PATTERN =
-        // eslint-disable-next-line max-len
-        /^#+[ \t]*(.*?)(?:]+)"[^>]*>\s*<\/a>)?((?:.(?!]*\sid="(.+?)"/gm;
+      // eslint-disable-next-line max-len
+      /^#+[ \t]*(.*?)(?:]+)"[^>]*>\s*<\/a>)?((?:.(?!]*\sid="(.+?)"/gm;
 
     const slugGenerator = new SlugGenerator();
 
     let match;
-    while (match = TITLE_PATTERN.exec(content)) {
+    while ((match = TITLE_PATTERN.exec(content))) {
       const title = match[1] + match[3];
       const anchor = match[2] || match[4] || match[5];
       if (anchor) {
@@ -207,7 +239,7 @@ class GrowReferenceChecker {
         }
       }
     }
-    this._anchorsByPage[this._getPathInPod(doc)]=anchors;
+    this._anchorsByPage[this._getPathInPod(doc)] = anchors;
     return doc;
   }
 
@@ -219,28 +251,29 @@ class GrowReferenceChecker {
    */
   _check(doc) {
     let content = doc.contents.toString();
-    content = content.replace(REFERENCE_PATTERN,
-        (match, hrefLink, hrefAnchor, markdownLink, markdownAnchor, gDocLink) => {
-          let result = match;
-          const link = hrefLink || markdownLink || gDocLink;
-          const anchor = hrefAnchor || markdownAnchor;
-          let resultLink;
-          if (link) {
-            resultLink = this._verifyReference(link, doc);
-            if (resultLink && resultLink != link) {
-              result = result.replace(link, resultLink);
-            }
+    content = content.replace(
+      REFERENCE_PATTERN,
+      (match, hrefLink, hrefAnchor, markdownLink, markdownAnchor, gDocLink) => {
+        let result = match;
+        const link = hrefLink || markdownLink || gDocLink;
+        const anchor = hrefAnchor || markdownAnchor;
+        let resultLink;
+        if (link) {
+          resultLink = this._verifyReference(link, doc);
+          if (resultLink && resultLink != link) {
+            result = result.replace(link, resultLink);
           }
-          // we will only check the anchor if the target page is found
-          if (anchor && !(link && !resultLink)) {
-            const newAnchor = this._checkAnchor(anchor,
-                resultLink, doc);
-            if (newAnchor != anchor) {
-              result = result.replace(anchor, newAnchor);
-            }
+        }
+        // we will only check the anchor if the target page is found
+        if (anchor && !(link && !resultLink)) {
+          const newAnchor = this._checkAnchor(anchor, resultLink, doc);
+          if (newAnchor != anchor) {
+            result = result.replace(anchor, newAnchor);
           }
-          return result;
-        });
+        }
+        return result;
+      }
+    );
 
     doc.contents = Buffer.from(content);
     return doc;
@@ -259,8 +292,15 @@ class GrowReferenceChecker {
       let targetPath = linkedPath.replace(/@[^.]+/, ''); // remove locale
       targetPath = this._resolveRelativeLink(targetPath, doc);
       if (sourcePath.includes('@')) {
-        localePaths.add(this._getPathForLocale(targetPath, sourcePath.substring(
-            sourcePath.indexOf('@') + 1, sourcePath.lastIndexOf('.'))));
+        localePaths.add(
+          this._getPathForLocale(
+            targetPath,
+            sourcePath.substring(
+              sourcePath.indexOf('@') + 1,
+              sourcePath.lastIndexOf('.')
+            )
+          )
+        );
       } else {
         for (const locale of config.getAvailableLocales()) {
           if (sourcePath == this._getPathForLocale(sourcePath, locale)) {
@@ -276,22 +316,38 @@ class GrowReferenceChecker {
     const errorLocales = [];
     for (const localePath of localePaths) {
       const foundAnchor = this._resolveAnchor(anchorValue, localePath);
-      if (!foundAnchor || resultAnchor && foundAnchor != resultAnchor) {
+      if (!foundAnchor || (resultAnchor && foundAnchor != resultAnchor)) {
         errorLocales.push(localePath);
       } else if (!resultAnchor) {
         resultAnchor = foundAnchor;
       }
     }
     if (errorLocales.length > 0) {
-      if (IMPORTED_DOCS.includes(sourcePath) ||
-          sourcePath.match(IGNORED_PATH_PATTERNS) && !sourcePath.includes('@')) {
-        this._log.warn('anchor not found in imported document', anchor, '\n',
-            'found in:', doc.path, '\n',
-            'target:', linkedPath ? errorLocales : '');
+      if (
+        IMPORTED_DOCS.includes(sourcePath) ||
+        (sourcePath.match(IGNORED_PATH_PATTERNS) && !sourcePath.includes('@'))
+      ) {
+        this._log.warn(
+          'anchor not found in imported document',
+          anchor,
+          '\n',
+          'found in:',
+          doc.path,
+          '\n',
+          'target:',
+          linkedPath ? errorLocales : ''
+        );
       } else {
-        this._log.error('anchor not found', anchor, '\n',
-            'found in:', doc.path, '\n',
-            'target:', linkedPath ? errorLocales : '');
+        this._log.error(
+          'anchor not found',
+          anchor,
+          '\n',
+          'found in:',
+          doc.path,
+          '\n',
+          'target:',
+          linkedPath ? errorLocales : ''
+        );
         this._wrongAnchorCount++;
       }
       return anchor;
@@ -300,8 +356,8 @@ class GrowReferenceChecker {
   }
 
   _getPathForLocale(filePath, locale) {
-    const pathWithLocale = filePath.substring(
-        0, filePath.lastIndexOf('.md')) + '@' + locale + '.md';
+    const pathWithLocale =
+      filePath.substring(0, filePath.lastIndexOf('.md')) + '@' + locale + '.md';
     if (this._anchorsByPage.hasOwnProperty(pathWithLocale)) {
       return pathWithLocale;
     } else {
@@ -356,8 +412,9 @@ class GrowReferenceChecker {
     if (changedPath) {
       if (changedPath.startsWith('/')) {
         changedPath = path.relative(
-            path.dirname(doc.path),
-            path.join(POD_BASE_PATH, changedPath));
+          path.dirname(doc.path),
+          path.join(POD_BASE_PATH, changedPath)
+        );
       }
       return changedPath;
     }
@@ -368,13 +425,17 @@ class GrowReferenceChecker {
     // If there is more than one match store all matches for the user to
     // do the manual fixing
     if (results.length > 1) {
-      this._log.error(`More than one possible match for ${documentPath}. Needs manual fixing.` +
-        ` (In ${doc.path})`);
+      this._log.error(
+        `More than one possible match for ${documentPath}. Needs manual fixing.` +
+          ` (In ${doc.path})`
+      );
       this._multipleMatches[documentPath] = results;
       return null;
     } else if (results.length == 0) {
-      this._log.error(`No matching document found for ${documentPath}. Needs manual fixing.` +
-        ` (First found in ${doc.path})`);
+      this._log.error(
+        `No matching document found for ${documentPath}. Needs manual fixing.` +
+          ` (First found in ${doc.path})`
+      );
       this._unfindableDocuments.push(documentPath);
       return null;
     }
@@ -435,16 +496,21 @@ class GrowReferenceChecker {
 
     // search for the same file in other dirs
     let results = search.recursiveSearchSync(
-        new RegExp(basename, 'i'), PAGES_BASE_PATH);
+      new RegExp(basename, 'i'),
+      PAGES_BASE_PATH
+    );
 
     if (results.length === 0) {
       const ext = path.extname(documentPath);
 
       // check if we can find the file with the other std extension
       if (ext === '.html' || ext === '.md') {
-        basename = path.basename(basename, ext) + (ext === '.md' ? '.html' : '.md');
+        basename =
+          path.basename(basename, ext) + (ext === '.md' ? '.html' : '.md');
         results = search.recursiveSearchSync(
-            new RegExp(basename, 'i'), PAGES_BASE_PATH);
+          new RegExp(basename, 'i'),
+          PAGES_BASE_PATH
+        );
       }
     }
     return results;
@@ -489,10 +555,12 @@ class GrowReferenceChecker {
       this._log.info('Add explicit anchors to:', pages);
 
       let stream = gulp.src(pages, {'read': true, 'base': './'});
-      stream = stream.pipe(through.obj((doc, encoding, callback) => {
-        this._addExplicitAnchorsForDoc(doc);
-        callback(null, doc);
-      }));
+      stream = stream.pipe(
+        through.obj((doc, encoding, callback) => {
+          this._addExplicitAnchorsForDoc(doc);
+          callback(null, doc);
+        })
+      );
       stream = stream.pipe(gulp.dest('./'));
       stream.on('end', () => {
         resolve();
@@ -505,18 +573,19 @@ class GrowReferenceChecker {
     const anchors = this._anchorsByPage[this._getPathInPod(doc)];
     const slugGenerator = new SlugGenerator();
     content = content.replace(
-        /^(#+)[ \t]*(.*?)(]*>\s*<\/a>)?((?:.(?! {
-          const headline = headlineStart + headlineEnd;
-          const slug = slugGenerator.generateSlug(headline);
-          const anchor = anchors[slug];
-          // The slug generator has to know all the headlines, since we want to generate slugs like github does.
-          // So only now do we check if we have an explicit anchor or our implicit anchor is not used.
-          if (anchorTag || !anchor || !anchor.isUsed) {
-            return line;
-          }
-          return `${hLevel} ${headline} `;
-        });
+      /^(#+)[ \t]*(.*?)(]*>\s*<\/a>)?((?:.(?! {
+        const headline = headlineStart + headlineEnd;
+        const slug = slugGenerator.generateSlug(headline);
+        const anchor = anchors[slug];
+        // The slug generator has to know all the headlines, since we want to generate slugs like github does.
+        // So only now do we check if we have an explicit anchor or our implicit anchor is not used.
+        if (anchorTag || !anchor || !anchor.isUsed) {
+          return line;
+        }
+        return `${hLevel} ${headline} `;
+      }
+    );
 
     doc.contents = Buffer.from(content);
     return doc;
diff --git a/platform/lib/utils/HeadDedupTransformer.js b/platform/lib/utils/HeadDedupTransformer.js
index bce4b37cc2b..8dcd904fb78 100644
--- a/platform/lib/utils/HeadDedupTransformer.js
+++ b/platform/lib/utils/HeadDedupTransformer.js
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-const {remove, firstChildByTag} = require('@ampproject/toolbox-optimizer').NodeUtils;
+const {
+  remove,
+  firstChildByTag,
+} = require('@ampproject/toolbox-optimizer').NodeUtils;
 const TAGS_TO_DEDUP = {
   meta: {
     name: 'viewport',
@@ -51,8 +54,8 @@ class HeadDedupTransformer {
       }
     }
     // remove duplicates
-    matches.forEach((matches) => {
-      matches.slice(1).forEach((node) => {
+    matches.forEach(matches => {
+      matches.slice(1).forEach(node => {
         remove(node);
       });
     });
@@ -63,7 +66,7 @@ class HeadDedupTransformer {
       if (node.tagName === key) {
         return this.matchAttributes(node, value) ? value : null;
       }
-    };
+    }
     return null;
   }
 
@@ -72,7 +75,7 @@ class HeadDedupTransformer {
       if (node.attribs[key] !== value) {
         return false;
       }
-    };
+    }
     return true;
   }
 }
diff --git a/platform/lib/utils/HeadDedupTransformer.test.js b/platform/lib/utils/HeadDedupTransformer.test.js
index 65e41342c1a..8791fdb69d6 100644
--- a/platform/lib/utils/HeadDedupTransformer.test.js
+++ b/platform/lib/utils/HeadDedupTransformer.test.js
@@ -3,7 +3,8 @@ const AmpOptimizer = require('@ampproject/toolbox-optimizer');
 const HeadDedupTransformer = require('./HeadDedupTransformer.js');
 
 test('removes duplicate canonical link and viewport', async () => {
-  expect(await transform(
+  expect(
+    await transform(
       `
   
   
@@ -12,11 +13,13 @@ test('removes duplicate canonical link and viewport', async () => {
   
 
 
-`,
-  )).toEqual(
-      '  ' +
-       ' ' +
-       '     ');
+`
+    )
+  ).toEqual(
+    '  ' +
+      ' ' +
+      '     '
+  );
 });
 
 async function transform(string) {
diff --git a/platform/lib/utils/cacheHelpers.js b/platform/lib/utils/cacheHelpers.js
index 09bbb74326a..a11a4e4490d 100644
--- a/platform/lib/utils/cacheHelpers.js
+++ b/platform/lib/utils/cacheHelpers.js
@@ -15,16 +15,21 @@
  */
 
 function setNoCache(response) {
-  response.setHeader('Cache-Control', 'private, no-cache, no-store, must-revalidate');
+  response.setHeader(
+    'Cache-Control',
+    'private, no-cache, no-store, must-revalidate'
+  );
 }
 
-function setMaxAge(response, maxAge, cdnMaxAge='') {
+function setMaxAge(response, maxAge, cdnMaxAge = '') {
   if (cdnMaxAge) {
     cdnMaxAge = `s-max-age=${cdnMaxAge}, `;
   }
   response.setHeader(
-      'Cache-Control',
-      `public, max-age=${maxAge}, ${cdnMaxAge}stale-while-revalidate=${Math.floor(maxAge * 2)}`,
+    'Cache-Control',
+    `public, max-age=${maxAge}, ${cdnMaxAge}stale-while-revalidate=${Math.floor(
+      maxAge * 2
+    )}`
   );
 }
 
@@ -37,7 +42,10 @@ function setNoSniff(response) {
 }
 
 function setHsts(response) {
-  response.setHeader('strict-transport-security', 'max-age=31536000; includeSubDomains; preload');
+  response.setHeader(
+    'strict-transport-security',
+    'max-age=31536000; includeSubDomains; preload'
+  );
 }
 
 function setXssProtection(response) {
@@ -45,7 +53,10 @@ function setXssProtection(response) {
 }
 
 function setAmpCSP(response) {
-  response.setHeader('content-security-policy', 'default-src * blob: data:; script-src blob: https://cdn.ampproject.org/esm/ https://cdn.ampproject.org/mp/ https://cdn.ampproject.org/rtv/ https://cdn.ampproject.org/sp/ https://cdn.ampproject.org/sw/ https://cdn.ampproject.org/v0.js https://cdn.ampproject.org/v0/ https://cdn.ampproject.org/viewer/; object-src \'none\'; style-src \'unsafe-inline\' https://cdn.ampproject.org/rtv/ https://cdn.materialdesignicons.com https://cloud.typography.com https://fast.fonts.net https://fonts.googleapis.com https://maxcdn.bootstrapcdn.com https://p.typekit.net https://pro.fontawesome.com https://use.fontawesome.com https://use.typekit.net; report-uri https://csp-collector.appspot.com/csp/amp');
+  response.setHeader(
+    'content-security-policy',
+    "default-src * blob: data:; script-src blob: https://cdn.ampproject.org/esm/ https://cdn.ampproject.org/mp/ https://cdn.ampproject.org/rtv/ https://cdn.ampproject.org/sp/ https://cdn.ampproject.org/sw/ https://cdn.ampproject.org/v0.js https://cdn.ampproject.org/v0/ https://cdn.ampproject.org/viewer/; object-src 'none'; style-src 'unsafe-inline' https://cdn.ampproject.org/rtv/ https://cdn.materialdesignicons.com https://cloud.typography.com https://fast.fonts.net https://fonts.googleapis.com https://maxcdn.bootstrapcdn.com https://p.typekit.net https://pro.fontawesome.com https://use.fontawesome.com https://use.typekit.net; report-uri https://csp-collector.appspot.com/csp/amp"
+  );
 }
 
 module.exports = {
diff --git a/platform/lib/utils/cheerioHelper.js b/platform/lib/utils/cheerioHelper.js
index 82416d279b6..7b306661b90 100644
--- a/platform/lib/utils/cheerioHelper.js
+++ b/platform/lib/utils/cheerioHelper.js
@@ -24,12 +24,12 @@
 function htmlContent(dom) {
   let html = dom.html();
   html = html.replace(
-      'xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink"',
-      'xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"',
+    'xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink"',
+    'xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"'
   );
   html = html.replace(
-      /xlink="http:\/\/www\.w3\.org\/1999\/xlink" href=/gm,
-      'xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href=',
+    /xlink="http:\/\/www\.w3\.org\/1999\/xlink" href=/gm,
+    'xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href='
   );
 
   // Ensure doctype has a line break before and after
diff --git a/platform/lib/utils/credentials.js b/platform/lib/utils/credentials.js
index ebdfad5d21f..c2af86a9483 100644
--- a/platform/lib/utils/credentials.js
+++ b/platform/lib/utils/credentials.js
@@ -44,7 +44,10 @@ function get(key) {
 
   if (!config.isProdMode() && !config.isStageMode()) {
     return Promise.reject(
-        new Error(`Environment variable ${ENV_PREFIX}${key.toUpperCase()} is not set.`));
+      new Error(
+        `Environment variable ${ENV_PREFIX}${key.toUpperCase()} is not set.`
+      )
+    );
   }
 
   return new Promise((resolve, reject) => {
diff --git a/platform/lib/utils/cssTransformer.js b/platform/lib/utils/cssTransformer.js
index e2d9db2911c..aaa157a783b 100644
--- a/platform/lib/utils/cssTransformer.js
+++ b/platform/lib/utils/cssTransformer.js
@@ -97,7 +97,7 @@ const SAFE_CLASS_NAMES = [
 ];
 
 rcs.selectorLibrary.setExclude(
-    new RegExp('^(?!' + SAFE_CLASS_NAMES.join('|') + ').*$'),
+  new RegExp('^(?!' + SAFE_CLASS_NAMES.join('|') + ').*$')
 );
 
 /**
@@ -142,9 +142,12 @@ class CssTransformer {
         continue;
       }
 
-      node.attribs.class = node.attribs.class.split(' ').map((className) => {
-        return rcs.selectorLibrary.get(className) || className;
-      }).join(' ');
+      node.attribs.class = node.attribs.class
+        .split(' ')
+        .map(className => {
+          return rcs.selectorLibrary.get(className) || className;
+        })
+        .join(' ');
     }
   }
 }
diff --git a/platform/lib/utils/git.js b/platform/lib/utils/git.js
index d926fbb6e71..efd7df3f0aa 100644
--- a/platform/lib/utils/git.js
+++ b/platform/lib/utils/git.js
@@ -15,10 +15,17 @@
  */
 const {execSync} = require('child_process');
 
-
-module.exports.version = execSync('git log -1 --pretty=format:%h ').toString().trim();
-module.exports.message = execSync('git log -1 --pretty=%B --no-merges').toString().trim();
-module.exports.user = execSync('git config user.name').toString().trim();
-module.exports.committerDate = (path) => {
-  return execSync(`git log --format=%ai ${path} | tail -1`).toString().trim();
+module.exports.version = execSync('git log -1 --pretty=format:%h ')
+  .toString()
+  .trim();
+module.exports.message = execSync('git log -1 --pretty=%B --no-merges')
+  .toString()
+  .trim();
+module.exports.user = execSync('git config user.name')
+  .toString()
+  .trim();
+module.exports.committerDate = path => {
+  return execSync(`git log --format=%ai ${path} | tail -1`)
+    .toString()
+    .trim();
 };
diff --git a/platform/lib/utils/googleSearch.js b/platform/lib/utils/googleSearch.js
index 86b64ceea2a..c6f9f67259c 100644
--- a/platform/lib/utils/googleSearch.js
+++ b/platform/lib/utils/googleSearch.js
@@ -29,16 +29,23 @@ const CSE_BASE_URL = 'https://www.googleapis.com/customsearch/v1';
 const CSE_ID = '014077439351665726204:s4tidjx0agu';
 let API_KEY = undefined;
 
-credentials.get('GOOGLE_CSE_API_KEY').then((key) => {
-  API_KEY = key;
-}).catch((err) => {
-  console.error('ERROR: Google site search will not be available!',
-      err.message ? err.message : err);
-});
+credentials
+  .get('GOOGLE_CSE_API_KEY')
+  .then(key => {
+    API_KEY = key;
+  })
+  .catch(err => {
+    console.error(
+      'ERROR: Google site search will not be available!',
+      err.message ? err.message : err
+    );
+  });
 
 async function search(query, locale, page, options = {}) {
   if (!API_KEY) {
-    throw Error('Custom search api key not initialized! Check log for errors on startup.');
+    throw Error(
+      'Custom search api key not initialized! Check log for errors on startup.'
+    );
   }
 
   const startIndex = (page - 1) * PAGE_SIZE + 1;
@@ -62,14 +69,17 @@ async function search(query, locale, page, options = {}) {
 
   const fetchResponse = await fetch(url.toString());
   if (!fetchResponse.ok) {
-    console.log(`CSE Error ${fetchResponse.status} for url ${url}: `, await fetchResponse.text());
+    console.log(
+      `CSE Error ${fetchResponse.status} for url ${url}: `,
+      await fetchResponse.text()
+    );
     throw Error('Invalid response for search query');
   }
 
   return await fetchResponse.json();
 }
 
-module.exports={
+module.exports = {
   search,
   PAGE_SIZE,
   MAX_PAGE,
diff --git a/platform/lib/utils/grow.js b/platform/lib/utils/grow.js
index 7313d3be2e4..8db05d14e40 100644
--- a/platform/lib/utils/grow.js
+++ b/platform/lib/utils/grow.js
@@ -25,11 +25,12 @@ const {project} = require('@lib/utils');
  */
 function exec(args) {
   return sh(
-      // to support local execution where grow is often not in the path, we add the default install path ~/bin
-      ['sh', '-c', `PATH=$PATH:~/bin && grow ${args}`],
-      {
-        workingDir: project.paths.GROW_POD,
-      });
+    // to support local execution where grow is often not in the path, we add the default install path ~/bin
+    ['sh', '-c', `PATH=$PATH:~/bin && grow ${args}`],
+    {
+      workingDir: project.paths.GROW_POD,
+    }
+  );
 }
 
 module.exports = exec;
diff --git a/platform/lib/utils/pageCache.js b/platform/lib/utils/pageCache.js
index e556e07ca0c..f6c918f850a 100644
--- a/platform/lib/utils/pageCache.js
+++ b/platform/lib/utils/pageCache.js
@@ -27,7 +27,9 @@ const fs = require('fs');
 const LRU = require('lru-cache');
 const gcpMetadata = require('gcp-metadata');
 
-const buildInfo = yaml.safeLoad(fs.readFileSync(utils.project.paths.BUILD_INFO_PATH, 'utf8'));
+const buildInfo = yaml.safeLoad(
+  fs.readFileSync(utils.project.paths.BUILD_INFO_PATH, 'utf8')
+);
 
 /**
  * Time in seconds an item in Redis will stay valid
@@ -80,28 +82,39 @@ const instance = (async () => {
 let redis = null;
 let lru = null;
 
-instance.then((instance) => {
-  // Check if there is an instance available. If there is, instantiate
-  // a client to use it, if there is none fall back to LRU cache
-  if (instance) {
-    console.log('[PAGE_CACHE]: Connecting to Redis', instance.port, instance.host);
-    try {
-      redis = new Redis(instance.port, instance.host);
-      console.log('[PAGE_CACHE]: Connected to Redis instance at',
-          instance.host, instance.port);
-      return;
-    } catch (e) {
-      console.error('[PAGE_CACHE]: Connecting to Redis failed', e);
+instance
+  .then(instance => {
+    // Check if there is an instance available. If there is, instantiate
+    // a client to use it, if there is none fall back to LRU cache
+    if (instance) {
+      console.log(
+        '[PAGE_CACHE]: Connecting to Redis',
+        instance.port,
+        instance.host
+      );
+      try {
+        redis = new Redis(instance.port, instance.host);
+        console.log(
+          '[PAGE_CACHE]: Connected to Redis instance at',
+          instance.host,
+          instance.port
+        );
+        return;
+      } catch (e) {
+        console.error('[PAGE_CACHE]: Connecting to Redis failed', e);
+      }
     }
-  }
 
-  console.warn('[PAGE_CACHE]: No Redis instances available. Falling back to LRU');
-  lru = new LRU({
-    max: LRU_MAX_ITEMS,
+    console.warn(
+      '[PAGE_CACHE]: No Redis instances available. Falling back to LRU'
+    );
+    lru = new LRU({
+      max: LRU_MAX_ITEMS,
+    });
+  })
+  .catch(e => {
+    console.error('[PAGE_CACHE]: Could not initialize caches', e);
   });
-}).catch((e) => {
-  console.error('[PAGE_CACHE]: Could not initialize caches', e);
-});
 
 /**
  * Prefixes the key (which should be the request URL) with the current
diff --git a/platform/lib/utils/project.js b/platform/lib/utils/project.js
index 4cf906465ac..73fcb20134d 100644
--- a/platform/lib/utils/project.js
+++ b/platform/lib/utils/project.js
@@ -66,8 +66,11 @@ const paths = {
   BUILD_INFO_PATH: absolute('platform/config/build-info.yaml'),
 };
 
-paths.FORMAT_COMPONENT_MAPPING =
-    path.join(paths.STATICS_DEST, 'files', 'format-component-mapping.json');
+paths.FORMAT_COMPONENT_MAPPING = path.join(
+  paths.STATICS_DEST,
+  'files',
+  'format-component-mapping.json'
+);
 
 module.exports = {
   absolute,
diff --git a/platform/lib/utils/sh.js b/platform/lib/utils/sh.js
index d810f67b52b..78dfafcded0 100644
--- a/platform/lib/utils/sh.js
+++ b/platform/lib/utils/sh.js
@@ -25,7 +25,6 @@ const DEFAULT_OPTIONS = {
   quiet: false,
 };
 
-
 /**
  * Executes a shell command.
  *
@@ -50,7 +49,7 @@ function sh(commandLine, ...options) {
     const process = spawn(command, args, {cwd: opts.workingDir});
     let result = '';
 
-    process.stdout.on('data', (data) => {
+    process.stdout.on('data', data => {
       data = data.toString();
       result += data;
       if (!opts.quiet) {
@@ -58,18 +57,18 @@ function sh(commandLine, ...options) {
       }
     });
 
-    process.stderr.on('data', (data) => {
+    process.stderr.on('data', data => {
       console.log(`${data.toString()}`);
     });
 
-    process.on('close', (code) => {
+    process.on('close', code => {
       if (code !== 0) {
         reject(new Error(`${command} process exited with code ${code}`));
         return;
       }
       resolve(result);
     });
-  }).then((result) => {
+  }).then(result => {
     console.log(message);
     return result;
   });
@@ -85,14 +84,14 @@ function extractOptions(params) {
 
 function extractCommandFragments(command) {
   if (typeof command === 'string') {
-    return command.replace(/\s+/gm, ' ')
-        .trim()
-        .split(' ');
+    return command
+      .replace(/\s+/gm, ' ')
+      .trim()
+      .split(' ');
   }
   return command;
 }
 
-
 function isString(obj) {
   return obj && obj.length > 0 && typeof obj[0] === 'string';
 }
diff --git a/platform/lib/utils/slugGenerator.js b/platform/lib/utils/slugGenerator.js
index 27e1ef785b2..429441494c3 100644
--- a/platform/lib/utils/slugGenerator.js
+++ b/platform/lib/utils/slugGenerator.js
@@ -32,7 +32,11 @@ class SlugGenerator {
   generateSlug(headline) {
     const slug = SlugGenerator.sluggify(headline);
     let result = slug;
-    for (let slugCounter = 1; this.existingSlugs.includes(result); slugCounter++) {
+    for (
+      let slugCounter = 1;
+      this.existingSlugs.includes(result);
+      slugCounter++
+    ) {
       result = slug + '-' + slugCounter;
     }
     this.existingSlugs.push(result);
diff --git a/platform/lib/utils/slugGenerator.test.js b/platform/lib/utils/slugGenerator.test.js
index d272588cf97..3fda057f674 100644
--- a/platform/lib/utils/slugGenerator.test.js
+++ b/platform/lib/utils/slugGenerator.test.js
@@ -4,7 +4,9 @@ test('Test anchor generation', () => {
   const slugGenerator = new SlugGenerator();
   expect(slugGenerator.generateSlug('')).toBe('');
   expect(slugGenerator.generateSlug('TestOne')).toBe('testone');
-  expect(slugGenerator.generateSlug('test two two two two two')).toBe('test-two-two-two-two-two');
+  expect(slugGenerator.generateSlug('test two two two two two')).toBe(
+    'test-two-two-two-two-two'
+  );
   expect(slugGenerator.generateSlug('test[{}](three)')).toBe('testthree');
   expect(slugGenerator.generateSlug('test four')).toBe('test-four');
   expect(slugGenerator.generateSlug('test four')).toBe('test-four-1');
diff --git a/platform/lib/utils/travis.js b/platform/lib/utils/travis.js
index 100e9b2a23d..72e6ddf2b1c 100644
--- a/platform/lib/utils/travis.js
+++ b/platform/lib/utils/travis.js
@@ -18,7 +18,6 @@
 
 require('module-alias/register');
 
-
 /**
  * Returns true if called inside a Travis environment
  * @return {Boolean}
diff --git a/playground/.eslintrc.json b/playground/.eslintrc.json
index 097bf458d39..fb01a777842 100644
--- a/playground/.eslintrc.json
+++ b/playground/.eslintrc.json
@@ -1,10 +1,9 @@
 {
   "parser": "babel-eslint",
-  "extends": "google",
   "parserOptions": {
     "ecmaVersion": 2018,
     "sourceType": "module"
-  },
+	},
   "rules": {
     "max-len": [2, 100, {
       "ignoreComments": true,
@@ -25,8 +24,7 @@
       "args": "after-used",
       "argsIgnorePattern": "(^reject$|^_$)",
       "varsIgnorePattern": "(^_$)"
-    }],
-    "quotes": [2, "single"],
+		}],
     "require-jsdoc": 0,
     "valid-jsdoc": 0,
     "prefer-arrow-callback": 1,
diff --git a/playground/backend/api.js b/playground/backend/api.js
index 67ad2fce9b0..15b365f7ff1 100644
--- a/playground/backend/api.js
+++ b/playground/backend/api.js
@@ -1,18 +1,18 @@
 /**
-* Copyright 2019 The AMPHTML Authors
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-*      http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
+ * Copyright 2019 The AMPHTML Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 const express = require('express');
 const fetch = require('node-fetch');
@@ -70,9 +70,10 @@ async function doFetch(url) {
     headers: {
       'Accept': 'text/html',
       'x-requested-by': 'playground',
-      'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MTC19V) '+
-      'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.81 Mobile '+
-      'Safari/537.36 (compatible; amp.dev/playground)',
+      'User-Agent':
+        'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MTC19V) ' +
+        'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.81 Mobile ' +
+        'Safari/537.36 (compatible; amp.dev/playground)',
       'Referer': 'https://amp.dev/playground',
     },
   });
diff --git a/playground/backend/index.js b/playground/backend/index.js
index d9eac6be6c5..6bd3f879229 100644
--- a/playground/backend/index.js
+++ b/playground/backend/index.js
@@ -1,18 +1,18 @@
 /**
-* Copyright 2019 The AMPHTML Authors
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-*      http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
+ * Copyright 2019 The AMPHTML Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 const path = require('path');
 const express = require('express');
@@ -24,21 +24,19 @@ const playground = express.Router();
 
 const BASE_DIR = path.join(__dirname, '../dist');
 
-playground.use(express.static(
-    BASE_DIR,
-    {
-      extensions: ['html'],
-      setHeaders: setCustomCacheControl,
-    },
-));
+playground.use(
+  express.static(BASE_DIR, {
+    extensions: ['html'],
+    setHeaders: setCustomCacheControl,
+  })
+);
 
 playground.use('/api', require('./api.js'));
 
 playground.use(robots('allow_all.txt'));
 
 // The fallback handler has to be last (kick in only if the request was not handled)
-playground.use( resourceFallbackHandler(BASE_DIR));
-
+playground.use(resourceFallbackHandler(BASE_DIR));
 
 function setCustomCacheControl(response, path) {
   // playground assets are versioned
diff --git a/playground/development.js b/playground/development.js
index 034e7c7ddc5..d8acdbdc5e8 100644
--- a/playground/development.js
+++ b/playground/development.js
@@ -1,18 +1,18 @@
 /**
-* Copyright 2019 The AMPHTML Authors
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-*      http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
+ * Copyright 2019 The AMPHTML Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 const express = require('express');
 const app = express();
diff --git a/playground/src/app.js b/playground/src/app.js
index e06240aa816..a58d99bd298 100644
--- a/playground/src/app.js
+++ b/playground/src/app.js
@@ -62,9 +62,8 @@ const errorIndicator = document.getElementById('error-indicator');
 const errorListContainer = document.getElementById('error-list');
 ErrorList.createErrorList(errorListContainer, errorIndicator);
 
-events.subscribe(
-    ErrorList.EVENT_ERROR_SELECTED,
-    (error) => editor.setCursorAndFocus(error.line, error.col),
+events.subscribe(ErrorList.EVENT_ERROR_SELECTED, error =>
+  editor.setCursorAndFocus(error.line, error.col)
 );
 
 const validator = Validator.createValidator();
@@ -72,45 +71,53 @@ const validator = Validator.createValidator();
 const componentsProvider = ComponentsProvider.createComponentsProvider();
 
 // Create AMP component auto-importer
-const autoImporter = AutoImporter.createAutoImporter(componentsProvider, editor);
+const autoImporter = AutoImporter.createAutoImporter(
+  componentsProvider,
+  editor
+);
 
 const emailLoader = EmailLoader.createEmailLoader(editor);
 
 // runtime select
-const runtimeChanged = (runtimeId) => {
+const runtimeChanged = runtimeId => {
   const newRuntime = runtimes.get(runtimeId);
   if (!newRuntime) {
     console.error('unknown runtime: ' + newRuntime);
     return;
-  };
+  }
   events.publish(EVENT_SET_RUNTIME, newRuntime);
 };
 
-const runtimeSelector = createSelector(document.getElementById('runtime-select'), {
-  classes: ['caret-right'],
-  id: 'runtime',
-  label: 'select runtime',
-  values: runtimes.values.map((r) => {
-    return {
-      id: r.id,
-      label: r.name,
-      selected: r === runtimes.activeRuntime,
-    };
-  }),
-  onChange: runtimeChanged,
-});
+const runtimeSelector = createSelector(
+  document.getElementById('runtime-select'),
+  {
+    classes: ['caret-right'],
+    id: 'runtime',
+    label: 'select runtime',
+    values: runtimes.values.map(r => {
+      return {
+        id: r.id,
+        label: r.name,
+        selected: r === runtimes.activeRuntime,
+      };
+    }),
+    onChange: runtimeChanged,
+  }
+);
 runtimeSelector.show();
 
 let activeRuntime;
-events.subscribe(EVENT_SET_RUNTIME, (newRuntime) => {
+events.subscribe(EVENT_SET_RUNTIME, newRuntime => {
   preview.setRuntime(newRuntime);
   runtimeSelector.selectOption(newRuntime.id);
   // change editor input to new runtime default if current input is unchanged
-  if (activeRuntime &&
+  if (
+    activeRuntime &&
     activeRuntime != newRuntime &&
-    activeRuntime.template === editor.getSource()) {
+    activeRuntime.template === editor.getSource()
+  ) {
     editor.setSource(newRuntime.template);
-  };
+  }
   validator.validate(editor.getSource());
   activeRuntime = newRuntime;
 
@@ -127,11 +134,8 @@ const editorUpdateListener = () => {
   validator.validate(source);
   titleUpdater.update(source);
 };
-events.subscribe(
-    [Editor.EVENT_INPUT_CHANGE],
-    editorUpdateListener,
-);
-events.subscribe(Validator.EVENT_NEW_VALIDATION_RESULT, (validationResult) => {
+events.subscribe([Editor.EVENT_INPUT_CHANGE], editorUpdateListener);
+events.subscribe(Validator.EVENT_NEW_VALIDATION_RESULT, validationResult => {
   editor.setValidationResult(validationResult);
 });
 events.subscribe([Editor.EVENT_INPUT_NEW], () => {
@@ -142,16 +146,16 @@ events.subscribe([Editor.EVENT_INPUT_NEW], () => {
 });
 
 // configure auto-importer
-events.subscribe(Validator.EVENT_NEW_VALIDATION_RESULT, (validationResult) => {
+events.subscribe(Validator.EVENT_NEW_VALIDATION_RESULT, validationResult => {
   autoImporter.update(validationResult);
 });
 
 // setup document
 const documentController = new DocumentController(
-    editor,
-    runtimes.activeRuntime,
-    document.querySelector('header'),
-    window,
+  editor,
+  runtimes.activeRuntime,
+  document.querySelector('header'),
+  window
 );
 documentController.show();
 
@@ -180,16 +184,16 @@ hidePreviewButton.addEventListener('click', closePreview);
 
 // load template dialog
 const loadTemplateButton = Button.from(
-    document.getElementById('document-title'),
-    () => templateDialog.open(runtimes.activeRuntime),
+  document.getElementById('document-title'),
+  () => templateDialog.open(runtimes.activeRuntime)
 );
 const templateDialog = createTemplateDialog(loadTemplateButton, {
   onStart: () => editor.showLoadingIndicator(),
-  onSuccess: (template) => {
+  onSuccess: template => {
     editor.setSource(template.content);
     params.replace('url', template.url);
   },
-  onError: (err) => {
+  onError: err => {
     snackbar.show(err);
   },
 });
@@ -201,15 +205,18 @@ Button.from(document.getElementById('show-menu'), () => {
 });
 
 const formatSource = () => {
-  formatter.format(editor.getSource()).then((formattedCode) => editor.setSource(formattedCode));
+  formatter
+    .format(editor.getSource())
+    .then(formattedCode => editor.setSource(formattedCode));
 };
 Button.from(document.getElementById('format-source'), formatSource);
 Button.from(document.getElementById('menu-format-source'), formatSource);
 
 const loadEmail = () => {
-  emailLoader.loadEmailFromFile()
-      .then(formatSource)
-      .catch((error) => alert(`Error loading email: ${error.message}`));
+  emailLoader
+    .loadEmailFromFile()
+    .then(formatSource)
+    .catch(error => alert(`Error loading email: ${error.message}`));
 };
 Button.from(document.getElementById('import-email'), loadEmail);
 
@@ -217,8 +224,7 @@ window.onpopstate = () => {
   if (!params.get('preview')) {
     previewPanel.classList.remove('show');
     showPreview.show();
-  };
+  }
 };
 
 showPreview.show();
-
diff --git a/playground/src/auto-importer/auto-importer.js b/playground/src/auto-importer/auto-importer.js
index 72cdcf64631..05dd3809252 100644
--- a/playground/src/auto-importer/auto-importer.js
+++ b/playground/src/auto-importer/auto-importer.js
@@ -17,7 +17,8 @@ import CodeMirror from 'codemirror';
 
 const ENGINE_MAP = {
   'amphtml engine v0.js script': '"https://cdn.ampproject.org/v0.js"',
-  'amp4ads engine amp4ads-v0.js script': '"https://cdn.ampproject.org/amp4ads-v0.js"',
+  'amp4ads engine amp4ads-v0.js script':
+    '"https://cdn.ampproject.org/amp4ads-v0.js"',
 };
 const ENGINE_SET = new Set();
 
@@ -41,7 +42,7 @@ class AutoImporter {
   constructor(componentsProvider, editor) {
     this.componentsProvider = componentsProvider;
     this.editor = editor;
-    Object.keys(ENGINE_MAP).forEach((k) => ENGINE_SET.add(ENGINE_MAP[k]));
+    Object.keys(ENGINE_MAP).forEach(k => ENGINE_SET.add(ENGINE_MAP[k]));
   }
 
   update(validationResult) {
@@ -56,11 +57,13 @@ class AutoImporter {
     if (currentTag.type === 'tag') {
       return;
     }
-    this.componentsProvider.get().then((components) => {
+    this.componentsProvider.get().then(components => {
       const missing = this._parseMissingElements(validationResult, components);
 
-      if (Object.keys(missing.missingTags).length ||
-          missing.missingBaseScriptTag) {
+      if (
+        Object.keys(missing.missingTags).length ||
+        missing.missingBaseScriptTag
+      ) {
         const existing = this._parseHeadTag();
         // The action taken to insert any elements to fix the report of missing
         // tags is determined by both a combination of looking at the list of
@@ -85,17 +88,17 @@ class AutoImporter {
       return;
     }
     const toAdd = Object.keys(missing.missingTags)
-        // Verify that all components to insert don't already exist: In some
-        // circumstances the validator has reported tags missing when in fact
-        // they are present.
-        .filter((e) => !existing.tags[e])
-        .map((e) => this._createAmpComponentElement(e, components));
+      // Verify that all components to insert don't already exist: In some
+      // circumstances the validator has reported tags missing when in fact
+      // they are present.
+      .filter(e => !existing.tags[e])
+      .map(e => this._createAmpComponentElement(e, components));
     if (missing.missingBaseScriptTag && !existing.baseScriptTagEnd) {
       const t = ``;
       toAdd.unshift(t);
     }
     if (toAdd.length) {
-      const indented = toAdd.map((e) => ' '.repeat(existing.indent || 0) + e);
+      const indented = toAdd.map(e => ' '.repeat(existing.indent || 0) + e);
       const cur = this.editor.getCursor();
       this.editor.replaceRange('\n' + indented.join('\n'), pos, pos);
       this.editor.setCursor(cur.line + indented.length, cur.ch);
@@ -143,7 +146,7 @@ class AutoImporter {
           }
           break;
         default:
-          // no default
+        // no default
       }
     }
     return missingElements;
@@ -206,8 +209,11 @@ class AutoImporter {
             } else if (tok.type === 'string' && ENGINE_SET.has(tok.string)) {
               inBaseScriptTag = true;
             }
-          } else if (htmlState.context.tagName === 'head' && tok.string === '>' &&
-              tok.type === 'tag bracket') {
+          } else if (
+            htmlState.context.tagName === 'head' &&
+            tok.string === '>' &&
+            tok.type === 'tag bracket'
+          ) {
             if (tagStart) {
               // Closing a `;
+    return (
+      ``
+    );
   }
 }
diff --git a/playground/src/components-provider/components-provider.js b/playground/src/components-provider/components-provider.js
index 390e0c15226..cc9ed26a49b 100644
--- a/playground/src/components-provider/components-provider.js
+++ b/playground/src/components-provider/components-provider.js
@@ -27,14 +27,14 @@ class ComponentsProvider {
           headers: new Headers({'x-requested-by': 'playground'}),
         });
         fetch(request)
-            .then((r) => r.json())
-            .then((data) => {
-              resolve(data);
-            })
-            .catch(() => {
-              console.warn('Failed to fetch AMP component versions mapping');
-              resolve({});
-            });
+          .then(r => r.json())
+          .then(data => {
+            resolve(data);
+          })
+          .catch(() => {
+            console.warn('Failed to fetch AMP component versions mapping');
+            resolve({});
+          });
       });
     });
   }
diff --git a/playground/src/document/controller.js b/playground/src/document/controller.js
index 27de72acc22..74f1582daea 100644
--- a/playground/src/document/controller.js
+++ b/playground/src/document/controller.js
@@ -39,18 +39,15 @@ export default class DocumentController {
       }
     });
     this._configureStatemachine();
+    events.subscribe(EVENT_INPUT_CHANGE, () => this.srcDoc.update());
     events.subscribe(
-        EVENT_INPUT_CHANGE,
-        () => this.srcDoc.update(),
-    );
-    events.subscribe(
-        PlaygroundDocument.EVENT_DOCUMENT_STATE_CHANGED,
-        this._onStateChange.bind(this),
+      PlaygroundDocument.EVENT_DOCUMENT_STATE_CHANGED,
+      this._onStateChange.bind(this)
     );
     win.addEventListener('hashchange', this._onHashChange.bind(this), false);
     // TODO find a better place for key handling
     key.filter = () => true;
-    key('⌘+s, ctrl+s', (e) => {
+    key('⌘+s, ctrl+s', e => {
       e.preventDefault();
       this.save();
     });
@@ -58,9 +55,9 @@ export default class DocumentController {
 
   _configureStatemachine() {
     this.statemachine = new Map()
-        .set(PlaygroundDocument.READ_ONLY, this._stateReadOnly)
-        .set(PlaygroundDocument.DIRTY, this._stateDirty)
-        .set(PlaygroundDocument.SAVED, this._stateSaved);
+      .set(PlaygroundDocument.READ_ONLY, this._stateReadOnly)
+      .set(PlaygroundDocument.DIRTY, this._stateDirty)
+      .set(PlaygroundDocument.SAVED, this._stateSaved);
   }
 
   _setupDocument(runtime) {
@@ -74,12 +71,13 @@ export default class DocumentController {
     } else {
       promise = Promise.resolve(runtime.template);
     }
-    return promise.then((content) => this.editor.setSource(content))
-        .catch((err) => {
-          console.error(err);
-          snackbar.show('Could not fetch document.');
-          this.editor.setSource(runtime.template);
-        });
+    return promise
+      .then(content => this.editor.setSource(content))
+      .catch(err => {
+        console.error(err);
+        snackbar.show('Could not fetch document.');
+        this.editor.setSource(runtime.template);
+      });
   }
 
   _getDocumentId() {
@@ -97,52 +95,57 @@ export default class DocumentController {
     params.replace('url', '');
     this.docId = docId;
     const path = this.win.location.pathname.replace(REGEX_DOC_ID, '/');
-    const newLocation = path + URL_DOC_ID_PREFIX + docId + this.win.location.hash;
+    const newLocation =
+      path + URL_DOC_ID_PREFIX + docId + this.win.location.hash;
     this.win.history.replaceState(null, null, newLocation);
   }
 
   show() {
     this.saveButton = Button.from(
-        this.win.document.getElementById('save-document'),
-        this.save.bind(this),
+      this.win.document.getElementById('save-document'),
+      this.save.bind(this)
     );
     this.forkButton = Button.from(
-        this.win.document.getElementById('fork-document'),
-        this.fork.bind(this),
+      this.win.document.getElementById('fork-document'),
+      this.fork.bind(this)
     );
     this._onStateChange(this.srcDoc.state, true);
   }
 
   fork() {
     this.forkButton.disable();
-    this.srcDoc.fork()
-        .then((docId) => {
-          this._setDocumentId(docId);
-          this.forkButton.enable();
-          snackbar.show('Document forked');
-        })
-        .catch((err) => {
-          console.error(err);
-          this.forkButton.enable();
-          snackbar.show('Could not fork document');
-        });
+    this.srcDoc
+      .fork()
+      .then(docId => {
+        this._setDocumentId(docId);
+        this.forkButton.enable();
+        snackbar.show('Document forked');
+      })
+      .catch(err => {
+        console.error(err);
+        this.forkButton.enable();
+        snackbar.show('Could not fork document');
+      });
   }
 
   save() {
-    if (this.srcDoc.state !== PlaygroundDocument.DIRTY &&
-        this.srcDoc.state !== PlaygroundDocument.READ_ONLY) {
+    if (
+      this.srcDoc.state !== PlaygroundDocument.DIRTY &&
+      this.srcDoc.state !== PlaygroundDocument.READ_ONLY
+    ) {
       return;
     }
     this.saveButton.disable();
-    this.srcDoc.save(this.editor.getSource())
-        .then((docId) => {
-          this._setDocumentId(docId);
-        })
-        .catch((err) => {
-          console.error(err);
-          this.saveButton.enable();
-          snackbar.show('Could not save document');
-        });
+    this.srcDoc
+      .save(this.editor.getSource())
+      .then(docId => {
+        this._setDocumentId(docId);
+      })
+      .catch(err => {
+        console.error(err);
+        this.saveButton.enable();
+        snackbar.show('Could not save document');
+      });
   }
 
   _onHashChange() {
@@ -164,9 +167,7 @@ export default class DocumentController {
 
   _stateSaved(disableSnackbar) {
     navigationWarning.disable();
-    this.saveButton
-        .setHtml('Saved')
-        .disable();
+    this.saveButton.setHtml('Saved').disable();
     if (disableSnackbar) {
       return;
     }
@@ -177,16 +178,16 @@ export default class DocumentController {
     if (!embedMode.isActive) {
       navigationWarning.enable();
     }
-    this.saveButton.show()
-        .setHtml('Save')
-        .enable();
+    this.saveButton
+      .show()
+      .setHtml('Save')
+      .enable();
   }
 
   _stateReadOnly() {
     if (!embedMode.isActive) {
       navigationWarning.enable();
     }
-    this.saveButton.hide()
-        .disable();
+    this.saveButton.hide().disable();
   }
 }
diff --git a/playground/src/document/document.js b/playground/src/document/document.js
index 1bbb1fc60c2..07c9d449daa 100644
--- a/playground/src/document/document.js
+++ b/playground/src/document/document.js
@@ -39,7 +39,7 @@ class PlaygroundDocument {
     return fetch('/api/fetch?url=' + url, {
       mode: 'cors',
       headers,
-    }).then((response) => {
+    }).then(response => {
       if (!response.ok) {
         throw new Error('Failed fetching document');
       }
@@ -54,21 +54,21 @@ class PlaygroundDocument {
       mode: 'cors',
       credentials: 'include',
     })
-        .then((response) => {
-          if (!response.ok) {
-            throw new Error('Failed fetching document');
-          }
-          return response.json();
-        })
-        .then((jsonDocument) => {
-          if (jsonDocument.readOnly) {
-            this._changeState(READ_ONLY);
-            this.docId = '';
-          } else {
-            this.docId = jsonDocument.id;
-          }
-          return jsonDocument.content;
-        });
+      .then(response => {
+        if (!response.ok) {
+          throw new Error('Failed fetching document');
+        }
+        return response.json();
+      })
+      .then(jsonDocument => {
+        if (jsonDocument.readOnly) {
+          this._changeState(READ_ONLY);
+          this.docId = '';
+        } else {
+          this.docId = jsonDocument.id;
+        }
+        return jsonDocument.content;
+      });
   }
 
   update() {
@@ -89,11 +89,12 @@ class PlaygroundDocument {
       method: 'POST',
       body: snippet,
       credentials: 'include',
-    }).then((response) => response.json())
-        .then((data) => {
-          this._changeState(SAVED);
-          return data.id;
-        });
+    })
+      .then(response => response.json())
+      .then(data => {
+        this._changeState(SAVED);
+        return data.id;
+      });
   }
 
   _changeState(newState) {
diff --git a/playground/src/editor/editor.js b/playground/src/editor/editor.js
index fbd98e7c29b..63225016140 100644
--- a/playground/src/editor/editor.js
+++ b/playground/src/editor/editor.js
@@ -38,12 +38,17 @@ require('./editor.scss');
 
 const DEFAULT_DEBOUNCE_RATE = 500;
 const HINT_IGNORE_ENDS = new Set([
-  ';', ',',
+  ';',
+  ',',
   ')',
-  '`', '"', '\'',
+  '`',
+  '"',
+  "'",
   '>',
-  '{', '}',
-  '[', ']',
+  '{',
+  '}',
+  '[',
+  ']',
 ]);
 const HINTS_URL = 'amphtml-hint.json';
 
@@ -155,12 +160,16 @@ class Editor {
   setValidationResult(validationResult) {
     this.codeMirror.clearGutter('CodeMirror-error-markers');
     this.codeMirror.operation(() => {
-      validationResult.errors.forEach((error) => {
+      validationResult.errors.forEach(error => {
         const marker = document.createElement('div');
         const message = marker.appendChild(document.createElement('span'));
         message.appendChild(document.createTextNode(error.message));
         marker.className = 'gutter-' + error.icon;
-        this.codeMirror.setGutterMarker(error.line - 1, 'CodeMirror-error-markers', marker);
+        this.codeMirror.setGutterMarker(
+          error.line - 1,
+          'CodeMirror-error-markers',
+          marker
+        );
       });
     });
   }
@@ -187,7 +196,7 @@ class Editor {
   }
 
   loadHints(validator) {
-    this.amphtmlHints.then((hints) => {
+    this.amphtmlHints.then(hints => {
       // eslint-disable-next-line no-unused-vars
       for (const key of Object.keys(CodeMirror.htmlSchema)) {
         delete CodeMirror.htmlSchema[key];
@@ -204,14 +213,16 @@ class Editor {
   fetchHintsData() {
     return new Promise((resolve, reject) => {
       window.requestIdleCallback(() => {
-        fetch(HINTS_URL).then((response) => {
-          if (response.status !== 200) {
-            return reject(new Error(`Error code ${response.status}`));
-          }
-          resolve(response.json());
-        }).catch((err) => {
-          reject(err);
-        });
+        fetch(HINTS_URL)
+          .then(response => {
+            if (response.status !== 200) {
+              return reject(new Error(`Error code ${response.status}`));
+            }
+            resolve(response.json());
+          })
+          .catch(err => {
+            reject(err);
+          });
       });
     });
   }
diff --git a/playground/src/email-loader/email-loader.js b/playground/src/email-loader/email-loader.js
index 9d69af17d43..d897535f002 100644
--- a/playground/src/email-loader/email-loader.js
+++ b/playground/src/email-loader/email-loader.js
@@ -26,7 +26,7 @@ class EmailLoader {
   }
 
   async loadEmailFromFile() {
-    const files = await new Promise((resolve) => {
+    const files = await new Promise(resolve => {
       const dialog = document.createElement('input');
       dialog.setAttribute('type', 'file');
       dialog.setAttribute('accept', '.eml');
@@ -49,15 +49,15 @@ class EmailLoader {
 
     const headers = this._parseHeaders(head);
     const {contentType, boundary} = this._parseMultipartContentType(
-        headers.get('content-type'),
+      headers.get('content-type')
     );
     if (contentType !== 'multipart/alternative') {
       throw new Error('Email is not multipart/alternative');
     }
     const parts = this._parseMultipartBody(body, boundary);
 
-    const ampPart = parts.find((part) =>
-      part.contentType.startsWith('text/x-amp-html'),
+    const ampPart = parts.find(part =>
+      part.contentType.startsWith('text/x-amp-html')
     );
     if (!ampPart) {
       throw new Error('No AMP part found in multipart/alternative');
@@ -79,12 +79,12 @@ class EmailLoader {
     }
 
     return new Map(
-        lines
-            .filter((line) => line)
-            .map((line) => {
-              const [key, value] = twoSplit(line, ':');
-              return [key.toLowerCase(), value.trim()];
-            }),
+      lines
+        .filter(line => line)
+        .map(line => {
+          const [key, value] = twoSplit(line, ':');
+          return [key.toLowerCase(), value.trim()];
+        })
     );
   }
 
@@ -98,7 +98,7 @@ class EmailLoader {
     }
     const parts = rawParts.slice(1, -1);
 
-    return parts.map((part) => {
+    return parts.map(part => {
       let [head, body] = twoSplit(part, '\n\n');
       if (!body) {
         throw new Error('No body found in email part');
@@ -139,9 +139,9 @@ class EmailLoader {
 // like String.prototype.split, but returns only two parts
 function twoSplit(str, separator) {
   const pos =
-    separator instanceof RegExp ?
-      str.search(separator) :
-      str.indexOf(separator);
+    separator instanceof RegExp
+      ? str.search(separator)
+      : str.indexOf(separator);
   if (pos === -1) {
     return [str];
   }
diff --git a/playground/src/embed-mode/index.js b/playground/src/embed-mode/index.js
index 95236cd2702..56378ae9f44 100644
--- a/playground/src/embed-mode/index.js
+++ b/playground/src/embed-mode/index.js
@@ -19,5 +19,3 @@ class EmbedMode {
 }
 
 export default new EmbedMode(document);
-
-
diff --git a/playground/src/error-list/error-list.js b/playground/src/error-list/error-list.js
index 51eaab131e9..e2eaf6377fd 100644
--- a/playground/src/error-list/error-list.js
+++ b/playground/src/error-list/error-list.js
@@ -31,34 +31,37 @@ class ErrorList {
     this.container = container;
     this.trigger = Button.from(trigger, this.toggle.bind(this));
     // configure validator
-    events.subscribe(Validator.EVENT_NEW_VALIDATION_RESULT, (validationResult) => {
-      this.update(validationResult);
-      window.requestIdleCallback(() => {
-        if (validationResult === Validator.NO_VALIDATOR) {
-          this.trigger.setHtml('valid');
-          this.trigger.disable();
-          return;
-        }
-        this.trigger.enable();
-        if (validationResult.status == 'PASS') {
-          this.trigger.disable();
-          return;
-        }
-        this.trigger.enable();
-        this.trigger.setHtml(
+    events.subscribe(
+      Validator.EVENT_NEW_VALIDATION_RESULT,
+      validationResult => {
+        this.update(validationResult);
+        window.requestIdleCallback(() => {
+          if (validationResult === Validator.NO_VALIDATOR) {
+            this.trigger.setHtml('valid');
+            this.trigger.disable();
+            return;
+          }
+          this.trigger.enable();
+          if (validationResult.status == 'PASS') {
+            this.trigger.disable();
+            return;
+          }
+          this.trigger.enable();
+          this.trigger.setHtml(
             validationResult.errors.length +
-          ' Error' +
-          (validationResult.errors.length > 1 ? 's' : ''));
-      });
-    });
+              ' Error' +
+              (validationResult.errors.length > 1 ? 's' : '')
+          );
+        });
+      }
+    );
   }
 
   update(validationResult) {
     this.validationResult = validationResult;
     window.requestIdleCallback(() => {
       /* eslint-disable max-len */
-      this.container.innerHTML =
-        `
+      this.container.innerHTML = `