diff --git a/.eslintrc.js b/.eslintrc.js index ef466922535d..c169e9833c2f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -30,5 +30,14 @@ module.exports = { document: "readonly", }, }, + { + files: ["**/*.ejs"], + plugins: ["ejs-js"], + rules: { + "no-redeclare": "off", + "no-inner-declarations": "off", + "no-useless-escape": "off", + }, + }, ], }; diff --git a/.npmignore b/.npmignore index 9bb73e6b4f43..18d20643cc95 100644 --- a/.npmignore +++ b/.npmignore @@ -18,6 +18,7 @@ server/node_modules/ /deployer kumascript/tests +kumascript/eslint-plugin-ejs-js testing/ import/ /mdn-web-docs.svg diff --git a/kumascript/eslint-plugin-ejs-js/License b/kumascript/eslint-plugin-ejs-js/License new file mode 100644 index 000000000000..2ac43c4a3ced --- /dev/null +++ b/kumascript/eslint-plugin-ejs-js/License @@ -0,0 +1,19 @@ +Copyright (c) 2018 Overlook Motel (theoverlookmotel@gmail.com) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. diff --git a/kumascript/eslint-plugin-ejs-js/README.md b/kumascript/eslint-plugin-ejs-js/README.md new file mode 100644 index 000000000000..1a27a02979ff --- /dev/null +++ b/kumascript/eslint-plugin-ejs-js/README.md @@ -0,0 +1,51 @@ +# eslint-plugin-ejs-js + +# EJS plugin for ESLint + +## Current status + +[![NPM version](https://img.shields.io/npm/v/eslint-plugin-ejs-js.svg)](https://www.npmjs.com/package/eslint-plugin-ejs-js) +[![Build Status](https://img.shields.io/travis/overlookmotel/eslint-plugin-ejs-js/master.svg)](http://travis-ci.org/overlookmotel/eslint-plugin-ejs-js) +[![Dependency Status](https://img.shields.io/david/overlookmotel/eslint-plugin-ejs-js.svg)](https://david-dm.org/overlookmotel/eslint-plugin-ejs-js) +[![Dev dependency Status](https://img.shields.io/david/dev/overlookmotel/eslint-plugin-ejs-js.svg)](https://david-dm.org/overlookmotel/eslint-plugin-ejs-js) +[![Greenkeeper badge](https://badges.greenkeeper.io/overlookmotel/eslint-plugin-ejs-js.svg)](https://greenkeeper.io/) +[![Coverage Status](https://img.shields.io/coveralls/overlookmotel/eslint-plugin-ejs-js/master.svg)](https://coveralls.io/r/overlookmotel/eslint-plugin-ejs-js) + +## Usage + +ESLint plugin for EJS (embedded Javascript templates). Implemented entirely in Javascript. + +``` +npm install --save-dev eslint eslint-plugin-ejs-js +``` + +Add `ejs-js` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix: + +```js +{ + "plugins": [ + "ejs-js" + ] +} +``` + +## Tests + +Use `npm test` to run the tests. Use `npm run cover` to check coverage. + +## Changelog + +See [changelog.md](https://github.com/overlookmotel/eslint-plugin-ejs-js/blob/master/changelog.md) + +## Issues + +If you discover a bug, please raise an issue on Github. https://github.com/overlookmotel/eslint-plugin-ejs-js/issues + +## Contribution + +Pull requests are very welcome. Please: + +- ensure all tests pass before submitting PR +- add an entry to changelog +- add tests for new features +- document new functionality/API additions in README diff --git a/kumascript/eslint-plugin-ejs-js/changelog.md b/kumascript/eslint-plugin-ejs-js/changelog.md new file mode 100644 index 000000000000..3742d31d01bb --- /dev/null +++ b/kumascript/eslint-plugin-ejs-js/changelog.md @@ -0,0 +1,5 @@ +# Changelog + +## 0.1.0 + +- Initial release diff --git a/kumascript/eslint-plugin-ejs-js/lib/index.js b/kumascript/eslint-plugin-ejs-js/lib/index.js new file mode 100644 index 000000000000..2a0ef0a034f7 --- /dev/null +++ b/kumascript/eslint-plugin-ejs-js/lib/index.js @@ -0,0 +1,244 @@ +/* -------------------- + * eslint-plugin-ejs-js module + * ------------------*/ + +"use strict"; + +/* + * Parser classes + */ +class Block { + constructor(line, col, print, precedingText) { + this.line = line; + this.col = col; + this.endLine = null; + this.endCol = null; + + this.outLine = null; + this.outCol = null; + this.outEndLine = null; + this.outEndCol = null; + + this.print = print || false; + this.preChars = (print ? 6 : 0) + (precedingText ? 8 : 0); + this.postChars = print ? 2 : 0; + this.precedingText = precedingText || false; + + this.text = undefined; + this.texts = []; + } + + add(text) { + this.texts.push(text.replace(/await/, "")); + } + + addFinal(thisText, endLine, endCol) { + this.add(thisText); + + let text = this.texts.join("\n"); + if (this.print) text = `print(${text});`; + if (this.precedingText) text = `print();${text}`; + this.text = text; + + this.endLine = endLine; + this.endCol = endCol; + } +} + +class Parser { + constructor(text) { + this.text = text; + this.lines = text.split(/\r?\n/); + this.lineNum = null; + this.outLineNum = 2; + this.outCol = 1; + this.blocks = []; + this.block = null; + this.precedingText = false; + this.line = null; + } + + parse() { + for (let i = 0; i < this.lines.length; i++) { + this.parseLine(i); + } + + // If open block, error + const { block, blocks } = this; + if (block) + throw new Error( + `Closing tag not found for block starting at line ${block.line}, column ${block.col}` + ); + + return `/* eslint-disable no-undef */\n${blocks + .map((block) => block.text) + .join("")}`; + } + + parseLine(index) { + this.lineNum = index + 1; + this.line = this.lines[index]; + this.lastCol = 0; + + if (this.block) { + // Block open + this.processEnd(); + } else { + this.processStart(); + } + } + + processStart() { + // Find start of block (skip over `<%%` and `<%#`) + const { line } = this; + let start = regexIndexOf(line, /<%([^%#]|$)/, this.lastCol); + if (start == -1) { + // No block on this line + this.precedingText = true; + return; + } + + if (start != this.lastCol) this.precedingText = true; + + // Identify if a start modifier + // TODO Support `<%#` and `<%%` + let print = false; + let endTag = start + 2; + const char = line.slice(endTag, endTag + 1); + if (["_", "=", "-"].includes(char)) { + endTag++; + if (char != "_") print = true; + } + + // Trim whitespace off start + let startText = regexIndexOf(line, /\S/, endTag); + if (startText == -1) startText = endTag; + + // Record block + const block = new Block( + this.lineNum, + startText + 1, + print, + this.precedingText + ); + block.outLine = this.outLineNum; + block.outCol = this.outCol + block.preChars; + this.block = block; + this.lastCol = startText; + + // Find end of block + this.processEnd(); + } + + processEnd() { + // Find end of block + const { line, lastCol, block } = this; + const end = line.indexOf("%>", lastCol); + if (end == -1) { + // End not found - add rest of line to block + block.add(line.slice(lastCol)); + this.outLineNum++; + this.outCol = 1; + return; + } + + // End found + // Remove end modifiers + let blockText = line.slice(lastCol, end); + let endText = end; + if (["-", "_"].includes(blockText.slice(-1))) { + endText--; + blockText = blockText.slice(0, -1); + } + + // Trim white space from end + if (lastCol != 0) { + const match = blockText.match(/\s+$/); + if (match) { + const chars = match[0].length; + blockText = blockText.slice(0, -chars); + endText -= chars; + } + } + + // Close block + block.addFinal(blockText, this.lineNum, endText + 1); + this.addBlock(block); + this.lastCol = end + 2; + this.precedingText = false; + + // Find next block + this.processStart(); + } + + addBlock(block) { + block.outEndLine = this.outLineNum; + + const blockLines = block.texts.length; + const outEndCol = + block.texts[blockLines - 1].length + (blockLines == 1 ? block.outCol : 1); + this.outCol = outEndCol + block.postChars; + block.outEndCol = outEndCol; + + this.blocks.push(block); + this.block = null; + } +} + +/* + * Processor + */ + +let blocks; + +function preprocess(text) { + const parser = new Parser(text); + const out = parser.parse(); + blocks = parser.blocks; + return [out]; +} + +function postprocess(messages) { + messages = messages[0]; + + if (!messages.length) return []; + + for (let message of messages) { + const { line, column: col } = message; + const block = blocks.find((block) => { + if (line < block.outLine) return false; + if (line > block.outEndLine) return false; + if (line == block.outLine && col < block.outCol) return false; + if (line == block.outEndLine && col > block.outEndCol) return false; + return true; + }); + + if (!block) throw new Error("Could not find matching block"); + + if (line == block.outLine) { + message.line = block.line; + message.column += block.col - block.outCol; + } else { + message.line += block.line - block.outLine; + } + } + + return messages; +} + +module.exports.processors = { + ".ejs": { + preprocess, + postprocess, + }, +}; + +/* + * Utiilty functions + */ + +function regexIndexOf(str, regex, pos) { + if (pos == null) pos = 0; + const indexOf = str.substring(pos).search(regex); + return indexOf == -1 ? -1 : pos + indexOf; +} diff --git a/kumascript/eslint-plugin-ejs-js/package.json b/kumascript/eslint-plugin-ejs-js/package.json new file mode 100644 index 000000000000..e58deaf4aa21 --- /dev/null +++ b/kumascript/eslint-plugin-ejs-js/package.json @@ -0,0 +1,46 @@ +{ + "name": "eslint-plugin-ejs-js", + "version": "0.1.0", + "description": "EJS plugin for ESLint", + "keywords": [ + "ejs", + "embedded", + "template", + "eslint" + ], + "bugs": { + "url": "https://github.com/overlookmotel/eslint-plugin-ejs-js/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/overlookmotel/eslint-plugin-ejs-js.git" + }, + "license": "MIT", + "author": { + "name": "Overlook Motel" + }, + "main": "./lib/", + "scripts": { + "cover": "npm run cover-main && rm -rf coverage", + "cover-main": "cross-env COVERAGE=true istanbul cover _mocha --report lcovonly -- -t 1000 -R spec \"test/**/*.test.js\"", + "coveralls": "npm run cover-main && cat ./coverage/lcov.info | coveralls && rm -rf ./coverage", + "lint": "eslint lib/** test/** *.js .*.js --ext .js", + "test": "npm run lint && npm run test-main", + "test-main": "mocha --check-leaks --colors -t 1000 -R spec \"test/**/*.test.js\"", + "travis": "if [ $COVERAGE ]; then npm run coveralls; else npm test; fi" + }, + "dependencies": {}, + "devDependencies": { + "chai": "^4.1.2", + "coveralls": "^3.0.2", + "cross-env": "^5.2.0", + "eslint": "^5.5.0", + "eslint-plugin-chai-friendly": "^0.4.1", + "istanbul": "^0.4.5", + "mocha": "^5.2.0" + }, + "engines": { + "node": ">=6" + }, + "readmeFilename": "README.md" +} diff --git a/package.json b/package.json index dd8f7abf5f4b..a343b70c3d36 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "build:client": "cd client && cross-env INLINE_RUNTIME_CHUNK=false react-scripts build", "build:ssr": "cd ssr && webpack --config webpack.config.js --mode=production", "dev": "yarn prepare-build && nf -j Procfile.dev start", - "eslint": "eslint \"content/**/*.js\" \"build/**/*.js\" \"kumascript/**/*.js\" \"tool/**/*.js\" \"server/**/*.js\" \"testing/**/*.js\"", + "eslint": "eslint \"content/**/*.js\" \"build/**/*.js\" \"kumascript/**/*.js\" \"tool/**/*.js\" \"server/**/*.js\" \"testing/**/*.js\" \"**/kumascript/macros/*.ejs\"", "filecheck": "cd filecheck && node cli.js", "fiori:build": "cd client && build-storybook", "fiori:start": "cd client && start-storybook -p 6006", @@ -97,6 +97,7 @@ "diff": "^5.0.0", "downshift": "^6.0.16", "eslint": "^7.18.0", + "eslint-plugin-ejs-js": "./kumascript/eslint-plugin-ejs-js", "eslint-plugin-jest": "24.1.3", "eslint-plugin-node": "11.1.0", "extend": "^3.0.2", diff --git a/yarn.lock b/yarn.lock index 58a169b3683e..bd8478b55d38 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7776,6 +7776,9 @@ eslint-module-utils@^2.6.0: debug "^2.6.9" pkg-dir "^2.0.0" +eslint-plugin-ejs-js@./kumascript/eslint-plugin-ejs-js: + version "0.1.0" + eslint-plugin-es@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893"