From 632a56415892ed8a34fd904efc6832837adecda6 Mon Sep 17 00:00:00 2001 From: knsv Date: Sat, 17 Oct 2015 10:39:20 +0200 Subject: [PATCH] Modernization, better linting, adjustment after stricter static rules --- .eslintignore | 1 + .eslintrc | 231 ++++++++++++++++++++++++++++++++++++++++++++++ .travis.yml | 2 +- package.json | 13 ++- src/mermaidAPI.js | 2 +- src/utils.js | 139 ++++++++++++++++++++++++++++ src/utils.spec.js | 183 ++++++++++++++++++++++++++++++++++++ 7 files changed, 564 insertions(+), 7 deletions(-) create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 src/utils.js create mode 100644 src/utils.spec.js diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..d2bbac818 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +**/parser/*.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 000000000..6705d9060 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,231 @@ +{ + "ecmaFeatures": { + "binaryLiterals": false, // enable binary literals + "blockBindings": false, // enable let and const (aka block bindings) + "defaultParams": false, // enable default function parameters + "forOf": false, // enable for-of loops + "generators": false, // enable generators + "objectLiteralComputedProperties": false, // enable computed object literal property names + "objectLiteralDuplicateProperties": false, // enable duplicate object literal properties in strict mode + "objectLiteralShorthandMethods": false, // enable object literal shorthand methods + "objectLiteralShorthandProperties": false, // enable object literal shorthand properties + "octalLiterals": false, // enable octal literals + "regexUFlag": false, // enable the regular expression u flag + "regexYFlag": false, // enable the regular expression y flag + "templateStrings": false, // enable template strings + "unicodeCodePointEscapes": false, // enable code point escapes + "jsx": false // enable JSX + }, + // I want to use babel-eslint for parsing! + "parser": "babel-eslint", + "env": { + // I write for browser + "browser": true, + // in CommonJS + "node": true + }, + // To give you an idea how to override rule options: + "rules": { + "quotes": [2, "single"], + "eol-last": [0], + "no-mixed-requires": [0], + "no-underscore-dangle": [0], + + + "no-alert": 0, + "no-array-constructor": 0, + "no-bitwise": 0, + "no-caller": 0, + "no-catch-shadow": 0, + "no-class-assign": 0, + "no-cond-assign": 2, + "no-console": 2, + "no-const-assign": 0, + "no-constant-condition": 2, + "no-continue": 0, + "no-control-regex": 2, + "no-debugger": 2, + "no-delete-var": 2, + "no-div-regex": 0, + "no-dupe-class-members": 0, + "no-dupe-keys": 2, + "no-dupe-args": 2, + "no-duplicate-case": 2, + "no-else-return": 0, + "no-empty": 2, + "no-empty-character-class": 2, + "no-empty-label": 0, + "no-empty-pattern": 0, + "no-eq-null": 0, + "no-eval": 0, + "no-ex-assign": 2, + "no-extend-native": 0, + "no-extra-bind": 0, + "no-extra-boolean-cast": 2, + "no-extra-parens": 0, + "no-extra-semi": 2, + "no-fallthrough": 2, + "no-floating-decimal": 0, + "no-func-assign": 2, + "no-implicit-coercion": 0, + "no-implied-eval": 0, + "no-inline-comments": 0, + "no-inner-declarations": [2, "functions"], + "no-invalid-regexp": 2, + "no-invalid-this": 0, + "no-irregular-whitespace": 2, + "no-iterator": 0, + "no-label-var": 0, + "no-labels": 0, + "no-lone-blocks": 0, + "no-lonely-if": 0, + "no-loop-func": 0, + "no-mixed-requires": [0, false], + "no-mixed-spaces-and-tabs": [2, false], + "linebreak-style": [0, "unix"], + "no-multi-spaces": 0, + "no-multi-str": 0, + "no-multiple-empty-lines": [0, {"max": 2}], + "no-native-reassign": 0, + "no-negated-condition": 0, + "no-negated-in-lhs": 2, + "no-nested-ternary": 0, + "no-new": 0, + "no-new-func": 0, + "no-new-object": 0, + "no-new-require": 0, + "no-new-wrappers": 0, + "no-obj-calls": 2, + "no-octal": 2, + "no-octal-escape": 0, + "no-param-reassign": 0, + "no-path-concat": 0, + "no-plusplus": 0, + "no-process-env": 0, + "no-process-exit": 0, + "no-proto": 0, + "no-redeclare": 2, + "no-regex-spaces": 2, + "no-restricted-modules": 0, + "no-restricted-syntax": 0, + "no-return-assign": 0, + "no-script-url": 0, + "no-self-compare": 0, + "no-sequences": 0, + "no-shadow": 0, + "no-shadow-restricted-names": 0, + "no-spaced-func": 0, + "no-sparse-arrays": 2, + "no-sync": 0, + "no-ternary": 0, + "no-trailing-spaces": 0, + "no-this-before-super": 0, + "no-throw-literal": 0, + "no-undef": 2, + "no-undef-init": 0, + "no-undefined": 0, + "no-unexpected-multiline": 0, + "no-underscore-dangle": 0, + "no-unneeded-ternary": 0, + "no-unreachable": 2, + "no-unused-expressions": 0, + "no-unused-vars": [2, {"vars": "all", "args": "after-used"}], + "no-use-before-define": 0, + "no-useless-call": 0, + "no-useless-concat": 0, + "no-void": 0, + "no-var": 0, + "no-warning-comments": [0, { "terms": ["todo", "fixme", "xxx"], "location": "start" }], + "no-with": 0, + "no-magic-numbers": 0, + + "array-bracket-spacing": [0, "never"], + "arrow-parens": 0, + "arrow-spacing": 0, + "accessor-pairs": 0, + "block-scoped-var": 0, + "block-spacing": 0, + "brace-style": [0, "1tbs"], + "callback-return": 0, + "camelcase": 0, + "comma-dangle": [2, "never"], + "comma-spacing": 0, + "comma-style": 0, + "complexity": [0, 11], + "computed-property-spacing": [0, "never"], + "consistent-return": 0, + "consistent-this": [0, "that"], + "constructor-super": 0, + "curly": [0, "all"], + "default-case": 0, + "dot-location": 0, + "dot-notation": [0, { "allowKeywords": true }], + "eol-last": 0, + "eqeqeq": 0, + "func-names": 0, + "func-style": [0, "declaration"], + "generator-star-spacing": 0, + "global-require": 0, + "guard-for-in": 0, + "handle-callback-err": 0, + "id-length": 0, + "indent": 0, + "init-declarations": 0, + "jsx-quotes": [0, "prefer-double"], + "key-spacing": [0, { "beforeColon": false, "afterColon": true }], + "lines-around-comment": 0, + "max-depth": [0, 4], + "max-len": [0, 80, 4], + "max-nested-callbacks": [0, 2], + "max-params": [0, 3], + "max-statements": [0, 10], + "new-cap": 0, + "new-parens": 0, + "newline-after-var": 0, + "object-curly-spacing": [0, "never"], + "object-shorthand": 0, + "one-var": [0, "always"], + "operator-assignment": [0, "always"], + "operator-linebreak": 0, + "padded-blocks": 0, + "prefer-arrow-callback": 0, + "prefer-const": 0, + "prefer-spread": 0, + "prefer-reflect": 0, + "prefer-template": 0, + "quote-props": 0, + "radix": 0, + "id-match": 0, + "require-jsdoc": 0, + "require-yield": 0, + "semi": 0, + "semi-spacing": [0, {"before": false, "after": true}], + "sort-vars": 0, + "space-after-keywords": [0, "always"], + "space-before-keywords": [0, "always"], + "space-before-blocks": [0, "always"], + "space-before-function-paren": [0, "always"], + "space-in-parens": [0, "never"], + "space-infix-ops": 0, + "space-return-throw-case": 0, + "space-unary-ops": [0, { "words": true, "nonwords": false }], + "spaced-comment": 0, + "strict": 0, + "use-isnan": 2, + "valid-jsdoc": 0, + "valid-typeof": 2, + "vars-on-top": 0, + "wrap-iife": 0, + "wrap-regex": 0, + "yoda": [0, "never"] + }, + "globals": { + "it": true, + "describe": true, + "expect": true, + "fit": true, + "beforeEach": true, + "fdescribe": true + } +} + diff --git a/.travis.yml b/.travis.yml index 01e60584a..08532c248 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "0.10" + - "4.1" addons: code_climate: repo_token: e87e6bf1c253e0555437ebd23235fdfe2749b889358e7c6d100e4ea5b4f2e091 diff --git a/package.json b/package.json index a880f0c54..b3fbe84cc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mermaid", - "version": "0.5.3-1", + "version": "0.5.3-2", "description": "Markdownish syntax for generating flowcharts, sequence diagrams and gantt charts.", "main": "src/mermaid.js", "keywords": [ @@ -19,10 +19,10 @@ "watch": "source ./scripts/watch.sh", "doc": "rm -r build;rm -r dist/www;gulp vartree;cp dist/www/all.html ../mermaid-pages/index.html;cp dist/mermaid.js ../mermaid-pages/javascripts/lib;cp dist/mermaid.forest.css ../mermaid-pages/stylesheets", "test": "npm run dist && ./node_modules/.bin/gulp test", - "dist-slim-mermaid": "browserify src/mermaid.js -s mermaid -o dist/mermaid.slim.js -x d3 && cat dist/mermaid.slim.js | uglifyjs -mc > dist/mermaid.slim.min.js", - "dist-slim-mermaidAPI": "browserify src/mermaidAPI.js -s mermaidAPI -o dist/mermaidAPI.slim.js -x d3 && cat dist/mermaidAPI.slim.js | uglifyjs -mc > dist/mermaidAPI.slim.min.js", - "dist-mermaid": "browserify src/mermaid.js -s mermaid -o dist/mermaid.js && cat dist/mermaid.js | uglifyjs -mc > dist/mermaid.min.js", - "dist-mermaidAPI": "browserify src/mermaidAPI.js -s mermaidAPI -o dist/mermaidAPI.js && cat dist/mermaidAPI.js | uglifyjs -mc > dist/mermaidAPI.min.js", + "dist-slim-mermaid": "node node_modules/browserify/bin/cmd.js src/mermaid.js -t babelify -s mermaid -o dist/mermaid.slim.js -x d3 && cat dist/mermaid.slim.js | uglifyjs -mc > dist/mermaid.slim.min.js", + "dist-slim-mermaidAPI": "node node_modules/browserify/bin/cmd.js src/mermaidAPI.js -t babelify -s mermaidAPI -o dist/mermaidAPI.slim.js -x d3 && cat dist/mermaidAPI.slim.js | uglifyjs -mc > dist/mermaidAPI.slim.min.js", + "dist-mermaid": "node node_modules/browserify/bin/cmd.js src/mermaid.js -t babelify -s mermaid -o dist/mermaid.js && cat dist/mermaid.js | uglifyjs -mc > dist/mermaid.min.js", + "dist-mermaidAPI": "node node_modules/browserify/bin/cmd.js src/mermaidAPI.js -t babelify -s mermaidAPI -o dist/mermaidAPI.js && cat dist/mermaidAPI.js | uglifyjs -mc > dist/mermaidAPI.min.js", "dist": "npm run dist-slim-mermaid;npm run dist-slim-mermaidAPI; npm run dist-mermaid;npm run dist-mermaidAPI" }, "repository": { @@ -86,6 +86,9 @@ "jison": "~0.4.15", "jsdom": "^6.5.1", "jshint-stylish": "^2.0.1", + "karma-babel-preprocessor": "^5.2.2", + "karma-browserify": "^4.4.0", + "karma-chrome-launcher": "^0.2.1", "map-stream": "0.0.6", "marked": "^0.3.2", "mock-browser": "^0.91.34", diff --git a/src/mermaidAPI.js b/src/mermaidAPI.js index 8b9002ae9..3ec296333 100644 --- a/src/mermaidAPI.js +++ b/src/mermaidAPI.js @@ -14,7 +14,7 @@ var graph = require('./diagrams/flowchart/graphDb'); var flow = require('./diagrams/flowchart/parser/flow'); -var utils = require('./utils-es6'); +var utils = require('./utils'); var flowRenderer = require('./diagrams/flowchart/flowRenderer'); var seq = require('./diagrams/sequenceDiagram/sequenceRenderer'); var info = require('./diagrams/example/exampleRenderer'); diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 000000000..7bdd772dc --- /dev/null +++ b/src/utils.js @@ -0,0 +1,139 @@ +/** + * Created by knut on 14-11-23. + */ +import * as Log from './logger'; +var log = Log.create(); + +/** + * @function detectType + * Detects the type of the graph text. + * ```mermaid + * graph LR + * a-->b + * b-->c + * c-->d + * d-->e + * e-->f + * f-->g + * g-->h + * ``` + * + * @param {string} text The text defining the graph + * @param {string} text The second text defining the graph + * @returns {string} A graph definition key + */ +var detectType = function(text,a){ + text = text.replace(/^\s*%%.*\n/g,'\n'); + if(text.match(/^\s*sequenceDiagram/)){ + return 'sequenceDiagram'; + } + + if(text.match(/^\s*digraph/)) { + //log.debug('Detected dot syntax'); + return 'dotGraph'; + } + + if(text.match(/^\s*info/)) { + //log.debug('Detected info syntax'); + return 'info'; + } + + if(text.match(/^\s*gantt/)) { + //log.debug('Detected info syntax'); + return 'gantt'; + } + + console.warn('detecting type!'); + + return 'graph'; +}; +export {detectType}; + +/** + * Copies all relevant CSS content into the graph SVG. + * This allows the SVG to be copied as is while keeping class based styling + * @param {element} svg The root element of the SVG + * @param {object} Hash table of class definitions from the graph definition + */ +var cloneCssStyles = function(svg, classes){ + console.warn('cloneCssStyles ----'); + var usedStyles = ''; + var sheets = document.styleSheets; + for (var i = 0; i < sheets.length; i++) { + // Avoid multiple inclusion on pages with multiple graphs + if (sheets[i].title !== 'mermaid-svg-internal-css') { + try { + + var rules = sheets[i].cssRules; + if (rules !== null) { + for (var j = 0; j < rules.length; j++) { + var rule = rules[j]; + if (typeof(rule.style) !== 'undefined') { + var elems; + elems = svg.querySelectorAll(rule.selectorText); + if (elems.length > 0) { + usedStyles += rule.selectorText + ' { ' + rule.style.cssText + ' }\n'; + } + } + } + } + } + catch(err) { + if(typeof console !== 'undefined'){ + if(console.warn !== 'undefined'){ + if(rule !== 'undefined'){ + console.warn('Invalid CSS selector "' + rule.selectorText + '"', err); + } + } + } + } + } + } + + var defaultStyles = ''; + var embeddedStyles = ''; + + for (var className in classes) { + if (classes.hasOwnProperty(className) && typeof(className) != 'undefined') { + if (className === 'default') { + if (classes.default.styles instanceof Array) { + defaultStyles += '#' + svg.id.trim() + ' .node' + '>rect { ' + classes[className].styles.join('; ') + '; }\n'; + } + if (classes.default.nodeLabelStyles instanceof Array) { + defaultStyles += '#' + svg.id.trim() + ' .node text ' + ' { ' + classes[className].nodeLabelStyles.join('; ') + '; }\n'; + } + if (classes.default.edgeLabelStyles instanceof Array) { + defaultStyles += '#' + svg.id.trim() + ' .edgeLabel text ' + ' { ' + classes[className].edgeLabelStyles.join('; ') + '; }\n'; + } + if (classes.default.clusterStyles instanceof Array) { + defaultStyles += '#' + svg.id.trim() + ' .cluster rect ' + ' { ' + classes[className].clusterStyles.join('; ') + '; }\n'; + } + } else { + if (classes[className].styles instanceof Array) { + embeddedStyles += '#' + svg.id.trim() + ' .' + className + '>rect { ' + classes[className].styles.join('; ') + '; }\n'; + } + } + } + } + + if (usedStyles !== '' || defaultStyles !== '' || embeddedStyles !== '') { + var s = document.createElement('style'); + s.setAttribute('type', 'text/css'); + s.setAttribute('title', 'mermaid-svg-internal-css'); + s.innerHTML = '/* */\n'; + svg.insertBefore(s, svg.firstChild); + } +}; + +export {cloneCssStyles}; \ No newline at end of file diff --git a/src/utils.spec.js b/src/utils.spec.js new file mode 100644 index 000000000..95cda44cd --- /dev/null +++ b/src/utils.spec.js @@ -0,0 +1,183 @@ +/** + * Created by knut on 14-11-23. + */ +import * as utils from './utils'; + +//var log = require('./logger').create(); +describe('when detecting chart type ', function () { + var str; + beforeEach(function () { + + }); + + it('should handle a graph defintion', function () { + str = 'graph TB\nbfs1:queue'; + + var type = utils.detectType(str); + expect(type).toBe('graph'); + }); + it('should handle a graph defintion with leading spaces', function () { + str = ' graph TB\nbfs1:queue'; + + var type = utils.detectType(str); + expect(type).toBe('graph'); + }); + + it('should handle a graph defintion with leading spaces and newline', function () { + str = ' \n graph TB\nbfs1:queue'; + + var type = utils.detectType(str); + expect(type).toBe('graph'); + }); +}); + +describe('when cloning CSS ', function () { + + + beforeEach(function () { + var MockBrowser = require('mock-browser').mocks.MockBrowser; + var mock = new MockBrowser(); + + // and in the run-code inside some object + global.document = mock.getDocument(); + //document.body.innerHTML = ''; + //document.body.innerHTML = ''; + }); + + function stylesToArray(svg) { + var styleSheets = svg.getElementsByTagName('style'); + expect(styleSheets.length).toBe(1); + var styleSheet = styleSheets[0]; + + var innerStyle = styleSheet.innerHTML; + var styleArr = innerStyle.split('\n'); + + // Remove first and last two lines to remove the CDATA + expect(styleArr.length).toBeGreaterThan(2); + var styleArrTrim = styleArr.slice(1, -2); + + // Remove all empty lines + for (var i = 0; i < styleArrTrim.length; i++) { + if (styleArrTrim[i].trim() === '') { + styleArrTrim.splice(i, 1); + i--; + } + } + + return styleArrTrim; + } + + function addStyleToDocument() { + var s = document.createElement('style'); + s.innerHTML = '.node { stroke:#eee; }\n.node-square { stroke:#bbb; }\n'; + document.body.appendChild(s); + } + + function addSecondStyleToDocument() { + var s = document.createElement('style'); + s.innerHTML = '.node2 { stroke:#eee; }\n.node-square { stroke:#beb; }\n'; + document.body.appendChild(s); + } + + function generateSVG() { + var svg = document.createElement('svg'); + svg.setAttribute('id', 'mermaid-01'); + var g1 = document.createElement('g'); + g1.setAttribute('class', 'node'); + svg.appendChild(g1); + var g2 = document.createElement('g'); + g2.setAttribute('class', 'node-square'); + svg.appendChild(g2); + return svg; + } + + function addMermaidSVGwithStyleToDocument() { + var styleSheetCount = document.styleSheets.length; + var svg = document.createElement('svg'); + svg.setAttribute('id', 'mermaid-03'); + var s = document.createElement('style'); + s.setAttribute('type', 'text/css'); + s.setAttribute('title', 'mermaid-svg-internal-css'); + s.innerHTML = '#mermaid-05 .node2 { stroke:#eee; }\n.node-square { stroke:#bfe; }\n'; + svg.appendChild(s); + document.body.appendChild(svg); + document.styleSheets[styleSheetCount].title = 'mermaid-svg-internal-css'; + } + + it('should handle an empty set of classes', function () { + var svg = document.createElement('svg'); + svg.setAttribute('id', 'mermaid-01'); + + utils.cloneCssStyles(svg, {}); + // Should not create style element if not needed + expect(svg.innerHTML).toBe(''); + }); + + it('should handle a default class', function () { + var svg = document.createElement('svg'); + svg.setAttribute('id', 'mermaid-01'); + + utils.cloneCssStyles(svg, {'default': {'styles': ['stroke:#fff', 'stroke-width:1.5px']}}); + expect(stylesToArray(svg)).toEqual(['#mermaid-01 .node>rect { stroke:#fff; stroke-width:1.5px; }']); + // Also verify the elements around the styling + expect(svg.innerHTML).toBe(''); + }); + + it('should handle stylesheet in document with no classes in SVG', function () { + var svg = document.createElement('svg'); + svg.setAttribute('id', 'mermaid-01'); + + addStyleToDocument('mermaid'); + utils.cloneCssStyles(svg, {}); + // Should not create style element if not needed + expect(svg.innerHTML).toBe(''); + }); + + it('should handle stylesheet in document with classes in SVG', function () { + var svg = generateSVG(); + addStyleToDocument(); + utils.cloneCssStyles(svg, {}); + expect(stylesToArray(svg)).toEqual(['.node { stroke: #eee; }', '.node-square { stroke: #bbb; }']); + }); + + it('should handle multiple stylesheets in document with classes in SVG', function () { + var svg = generateSVG(); + addStyleToDocument(); + addSecondStyleToDocument(); + utils.cloneCssStyles(svg, {}); + expect(stylesToArray(svg)).toEqual(['.node { stroke: #eee; }', '.node-square { stroke: #bbb; }', '.node-square { stroke: #beb; }']); + }); + + it('should handle multiple stylesheets + ignore styles in other mermaid SVG', function () { + var svg = generateSVG(); + addStyleToDocument(); + addSecondStyleToDocument(); + addMermaidSVGwithStyleToDocument(); + utils.cloneCssStyles(svg, {}); + expect(stylesToArray(svg)).toEqual(['.node { stroke: #eee; }', '.node-square { stroke: #bbb; }', '.node-square { stroke: #beb; }']); + }); + + it('should handle a default class together with stylesheet in document with classes in SVG', function () { + var svg = generateSVG(); + addStyleToDocument(); + utils.cloneCssStyles(svg, {'default': {'styles': ['stroke:#fff', 'stroke-width:1.5px']}}); + expect(stylesToArray(svg)).toEqual(['#mermaid-01 .node>rect { stroke:#fff; stroke-width:1.5px; }', '.node { stroke: #eee; }', '.node-square { stroke: #bbb; }']); + }); + + it('should handle a default class together with stylesheet in document and classDefs', function () { + var svg = generateSVG(); + addStyleToDocument(); + utils.cloneCssStyles(svg, { + 'default': {'styles': ['stroke:#fff', 'stroke-width:1.5px']}, + 'node-square': {'styles': ['fill:#eee', 'stroke:#aaa']}, + 'node-circle': {'styles': ['fill:#444', 'stroke:#111']} + }); + expect(stylesToArray(svg)).toEqual(['#mermaid-01 .node>rect { stroke:#fff; stroke-width:1.5px; }', + '.node { stroke: #eee; }', + '.node-square { stroke: #bbb; }', + '#mermaid-01 .node-square>rect { fill:#eee; stroke:#aaa; }', + '#mermaid-01 .node-circle>rect { fill:#444; stroke:#111; }' + ]); + }); +}); +