me.json
{
"name": "Elijah Manor",
"priorities" : [
"Christian", "Family", "Work"
],
"work" : [
"@LeanKit", "@PluralSight"
],
"tech" : [
"HTML", "CSS", "JavaScript",
"React", "jQuery"
],
"titles" : [
"Microsoft MVP",
"IE userAgent"
]
}
"To lint, or not to lint, that is the question--
Whether 'tis Nobler in the mind to suffer
The Slings and Arrows of outrageous bugs,
Or to take Arms against JavaScript's bad parts,
And by opposing, end them?"
--William Shakespeare's play Hamlet
"I lint, therefore I am" --Descartes
"pig" → "igpay"
"banana" → "ananabay"
"trash" → "ashtray"
"happy" → "appyhay"
"glove" → "oveglay"
"egg" → "eggway"
"inbox" → "inboxway"
"eight" → "eightway"
/* const */ var CONSONANTS = 'bcdfghjklmnpqrstvwxyz';
/* const */ var VOWELS = 'aeiou';
function englishToPigLatin(english) {
/* const */ var SYLLABLE = 'ay';
var pigLatin = '';
if (english !== null && english.length > 0 &&
(VOWELS.indexOf(english[0]) > -1 ||
CONSONANTS.indexOf(english[0]) > -1 )) {
if (VOWELS.indexOf(english[0]) > -1) {
pigLatin = english + SYLLABLE;
} else {
var preConsonants = '';
for (var i = 0; i < english.length; ++i) {
if (CONSONANTS.indexOf(english[i]) > -1) {
preConsonants += english[i];
if (preConsonants == 'q' &&
i+1 < english.length && english[i+1] == 'u') {
preConsonants += 'u';
i += 2;
break;
}
} else { break; }
}
pigLatin = english.substring(i) + preConsonants + SYLLABLE;
}
}
return pigLatin;
}
"... is a software metric (measurement), used to indicate the complexity of a program. It is a quantitative measure of the number of linearly independent paths through a program's source code. It was developed by Thomas J. McCabe, Sr. in 1976." --wikipedia
/*jshint maxstatements:15, maxdepth:2, maxcomplexity:5 */
/*eslint max-statements:[2, 15], max-depth:[1, 2], complexity:[2, 5] */
7:0 - Function 'englishToPigLatin' has a complexity of 7.
7:0 - This function has too many statements (16). Maximum allowed is 15.
22:10 - Blocks are nested too deeply (5).
max-statements:[2, 16]
max-depth:[2, 5]
complexity:[2, 7]
max-len:[2, 65]
max-params:[2, 1]
max-nested-callbacks:[2, 0]
describe('Pig Latin', function() {
describe('Invalid', function() {
it('should return blank if passed null', function() {
expect(englishToPigLatin(null)).toBe('');
});
it('should return blank if passed blank', function() {
expect(englishToPigLatin('')).toBe('');
});
it('should return blank if passed number', function() {
expect(englishToPigLatin('1234567890')).toBe('');
});
it('should return blank if passed symbol', function() {
expect(englishToPigLatin('~!@#$%^&*()_+')).toBe('');
});
});
describe('Consonants', function() {
it('should return eastbay from beast', function() {
expect(englishToPigLatin('beast')).toBe('eastbay');
});
it('should return estionquay from question', function() {
expect(englishToPigLatin('question')).toBe('estionquay');
});
it('should return eethray from three', function() {
expect(englishToPigLatin('three')).toBe('eethray');
});
});
describe('Vowels', function() {
it('should return appleay from apple', function() {
expect(englishToPigLatin('apple')).toBe('appleay');
});
});
});
const CONSONANTS = ['th', 'qu', 'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k',
'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z'];
const VOWELS = ['a', 'e', 'i', 'o', 'u'];
const ENDING = 'ay';
let isValid = word => startsWithVowel(word) || startsWithConsonant(word);
let startsWithVowel = word => !!~VOWELS.indexOf(word[0]);
let startsWithConsonant = word => !!~CONSONANTS.indexOf(word[0]);
let getConsonants = word => CONSONANTS.reduce((memo, char) => {
if (word.startsWith(char)) {
memo += char;
word = word.substr(char.length);
}
return memo;
}, '');
function englishToPigLatin(english='') {
if (isValid(english)) {
if (startsWithVowel(english)) {
english += ENDING;
} else {
let letters = getConsonants(english);
english = `${english.substr(letters.length)}${letters}${ENDING}`;
}
}
return english;
}
jshint
- http://jshint.com/eslint
- http://eslint.org/jscomplexity
- http://jscomplexity.org/escomplex
- https://github.com/philbooth/escomplexjasmine
- http://jasmine.github.io/
Box.js
// ... more code ...
var boxes = document.querySelectorAll('.Box');
[].forEach.call(boxes, function(element, index) {
element.innerText = "Box: " + index;
element.style.backgroundColor =
'#' + (Math.random() * 0xFFFFFF << 0).toString(16);
});
// ... more code ...
Circle.js
// ... more code ...
var circles = document.querySelectorAll(".Circle");
[].forEach.call(circles, function(element, index) {
element.innerText = "Circle: " + index;
element.style.color =
'#' + (Math.random() * 0xFFFFFF << 0).toString(16);
});
// ... more code ...
jsinspect
Detect copy-pasted and structurally similar code
jsinspect
jscpd
Copy/paste detector for programming source code (JavaScript, TypeScript, C#, Ruby, CSS, SCSS, HTML, etc...)
jscpd -f **/*.js -l 1 -t 30 --languages javascript
Let's pull out the random color portion...
let randomColor = () => `#${(Math.random() * 0xFFFFFF << 0).toString(16)};
let boxes = document.querySelectorAll(".Box");
[].forEach.call(boxes, (element, index) => {
element.innerText = "Box: " + index;
element.style.backgroundColor = randomColor(); // 6: Refactored
});
let circles = document.querySelectorAll(".Circle");
[].forEach.call(circles, (element, index) => {
element.innerText = "Circle: " + index;
element.style.color = randomColor(); // 12: Refactored
});
Let's pull out the weird [].forEach.call
portion...
let randomColor = () => `#${(Math.random() * 0xFFFFFF << 0).toString(16)};
let $$ = selector => [].slice.call(document.querySelectorAll(selector || '*'));
$$('.Box').forEach((element, index) => { // 5: Refactored
element.innerText = "Box: " + index;
element.style.backgroundColor = randomColor();
});
$$(".Circle").forEach((element, index) => { // 10: Refactored
element.innerText = "Circle: " + index;
element.style.color = randomColor();
});
Let's try to go further...
let randomColor = () => `#${(Math.random() * 0xFFFFFF << 0).toString(16)};
let $$ = selector => [].slice.call(document.querySelectorAll(selector || '*'));
let updateElement = (selector, textPrefix, styleProperty) => {
$$(selector).forEach((element, index) => {
element.innerText = textPrefix + ': ' + index;
element.style[styleProperty] = randomColor();
});
}
updateElement('.Box', 'Box', 'backgroundColor'); // 12: Refactored
updateElement('.Circle', 'Circle', 'color'); // 14: Refactored
function getArea(shape, options) {
var area = 0;
switch (shape) {
case 'Triangle':
area = .5 * options.width * options.height;
break;
case 'Square':
area = Math.pow(options.width, 2);
break;
case 'Rectangle':
area = options.width * options.height;
break;
default:
throw new Error('Invalid shape: ' + shape);
}
return area;
}
getArea('Triangle', { width: 100, height: 100 });
getArea('Square', { width: 100 });
getArea('Rectangle', { width: 100, height: 100 });
getArea('Bogus');
"...software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification; that is, such an entity can allow its behaviour to be extended without modifying its source code." --wikipedia
function getArea(shape, options) {
var area = 0;
switch (shape) {
case 'Triangle':
area = .5 * options.width * options.height;
break;
case 'Square':
area = Math.pow(options.width, 2);
break;
case 'Rectangle':
area = options.width * options.height;
break;
case 'Circle': // 17: New Shape Type
area = Math.PI * Math.pow(options.radius, 2);
break;
default:
throw new Error('Invalid shape: ' + shape);
}
return area;
}
(function(shapes) { // triangle.js
var Triangle = shapes.Triangle = function(options) {
this.width = options.width;
this.height = options.height;
};
Triangle.prototype.getArea = function() {
return 0.5 * this.width * this.height;
};
}(window.shapes = window.shapes || {}));
function getArea(shape, options) {
var Shape = window.shapes[shape], area = 0;
if (Shape && typeof Shape === 'function') {
area = new Shape(options).getArea();
} else {
throw new Error('Invalid shape: ' + shape);
}
return area;
}
getArea('Triangle', { width: 100, height: 100 });
getArea('Square', { width: 100 });
getArea('Rectangle', { width: 100, height: 100 });
getArea('Bogus');
// circle.js
(function(shapes) {
var Circle = shapes.Circle = function(options) {
this.radius = options.radius;
};
Circle.prototype.getArea = function() {
return Math.PI * Math.pow(this.radius, 2);
};
Circle.prototype.getCircumference = function() {
return 2 * Math.PI * this.radius;
};
}(window.shapes = window.shapes || {}));
function getArea(shape, options) {
var area = 0;
switch (shape) {
case 'Triangle': // 5: Magic String
area = .5 * options.width * options.height;
break;
/* ... more code ... */
}
return area;
}
getArea('Triangle', { width: 100, height: 100 }); // 14: Magic String
var shapeType = {
triangle: 'Triangle' // 2: Object Type
};
function getArea(shape, options) {
var area = 0;
switch (shape) {
case shapeType.triangle: // 8: Object Type
area = .5 * options.width * options.height;
break;
}
return area;
}
getArea(shapeType.triangle, { width: 100, height: 100 }); // 15: Object Type
const
& symbols
const shapeType = {
triangle: Symbol() // 2: Enum-ish
};
function getArea(shape, options) {
var area = 0;
switch (shape) {
case shapeType.triangle: // 8: Enum-ish
area = .5 * options.width * options.height;
break;
}
return area;
}
getArea(shapeType.triangle, { width: 100, height: 100 }); // 15: Enum-ish
eslint-plugin-smells
ESLint rules for JavaScript Smells http://bit.ly/eslint-plugin-smells
no-switch
- disallow the use of the switch
statementno-complex-switch-case
- disallow use of complex switch
statements/*eslint no-switch:[0] */
switch (test) { case 'val': break; }
/*eslint-disable */
switch (test) { case 'val': break; }
/*eslint-enable */
/*eslint-disable no-switch */
switch (test) { case 'val': break; }
/*eslint-enable no-switch */
switch (test) { case 'val': break; } // eslint-disable-line
switch (test) { case 'val': break; } // eslint-disable-line no-switch
function Person() {
this.teeth = [{ clean: false }, { clean: false }, { clean: false }];
};
Person.prototype.brush = function() {
var that = this;
this.teeth.forEach(function(tooth) {
that.clean(tooth);
});
console.log('brushed');
};
Person.prototype.clean = function(tooth) {
tooth.clean = true;
}
var person = new Person();
person.brush();
console.log(person.teeth);
that
or self
or selfie
function Person() {
this.teeth = [{ clean: false }, { clean: false }, { clean: false }];
};
Person.prototype.brush = function() {
var that = this; // 6: Stink
this.teeth.forEach(function(tooth) {
that.clean(tooth); // 9: Stink
});
console.log('brushed');
};
Person.prototype.clean = function(tooth) {
tooth.clean = true;
}
var person = new Person();
person.brush();
console.log(person.teeth);
1) bind
Person.prototype.brush = function() {
this.teeth.forEach(function(tooth) {
this.clean(tooth);
}.bind(this)); // 4: Use .bind() to change context
console.log('brushed');
};
2) 2nd parameter of forEach
Person.prototype.brush = function() {
this.teeth.forEach(function(tooth) {
this.clean(tooth);
}, this); // 4: Use 2nd parameter of .forEach to change context
console.log('brushed');
};
3) ECMAScript 2015 (ES6)
Person.prototype.brush = function() {
this.teeth.forEach(tooth => { // 2: Use ES6 Arrow Function to bind `this`
this.clean(tooth);
});
console.log('brushed');
};
4a) Functional Programming
Person.prototype.brush = function() {
this.teeth.forEach(this.clean); // 2: Use functional programming
console.log('brushed');
};
4b) Functional Programming
Person.prototype.brush = function() {
this.teeth.forEach(this.clean.bind(this)); // 2: Bind `this` if clean needs it
console.log('brushed');
};
no-this-assign
consistent-this
no-extra-bind
var build = function(id, href, text) {
return $( "<div id='tab'><a href='" + href + "' id='" + id + "'>" +
text + "</a></div>" );
}
1) Tweet Sized JavaScript Templating Engine by @thomasfuchs
function t(s, d) {
for (var p in d)
s = s.replace(new RegExp('{' + p + '}', 'g'), d[p]);
return s;
}
var build = function(id, href, text) {
var options = {
id: id,
href: href,
text: text
};
return t('<div id="tab"><a href="{href}" id="{id}">{text}</></div>', options);
}
2) ECMAScript 2015 (ES6) Template Strings
var build = (id, href, text) =>
`<div id="tab"><a href="${href}" id="${id}">${text}</a></div>`;
3) ECMAScript 2015 (ES6) Template Strings (Multiline)
var build = (id, href, text) => `<div id="tab">
<a href="${href}" id="${id}">${text}</a>
</div>`;
4a) ECMAScript 2015 (ES6) Tagged Template Strings to Protect Against XSS (Cross Site Scripting)
var build = (id, href, text) => SanitizeHTML`<div id="tab">
<a href="${href}" id="${id}">${text}</a>
</div>`;
4b) ECMAScript 2015 (ES6) Tagged Template Strings
function SanitizeHTML(literals, ...values) {
let string = '';
values.forEach((value, i) => {
value = String(value);
string += literals[i];
string += value.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>');
});
string += literals[literals.length - 1];
return string;
}
4) Other micro-libraries or larger libraries/frameworks
eslint-plugin-smells
$(document).ready(function() {
$('.Component')
.find('button')
.addClass('Component-button--action')
.click(function() { alert('HEY!'); })
.end()
.mouseenter(function() { $(this).addClass('Component--over'); })
.mouseleave(function() { $(this).removeClass('Component--over'); })
.addClass('initialized');
});
// Event Delegation before DOM Ready
$(document).on('mouseenter mouseleave', '.Component', function(e) {
$(this).toggleClass('Component--over', e.type === 'mouseenter');
});
$(document).on('click', '.Component button', function(e) {
alert('HEY!');
});
$(document).ready(function() {
$('.Component button').addClass('Component-button--action');
});
eslint-plugin-smells
setInterval(function() {
console.log('start setInterval');
someLongProcess(getRandomInt(2000, 4000));
}, 3000);
function someLongProcess(duration) {
setTimeout(
function() { console.log('long process: ' + duration); },
duration
);
}
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
setInternval
setTimeout
setTimeout
setTimeout(function timer() {
console.log('start setTimeout')
someLongProcess(getRandomInt(2000, 4000), function() {
setTimeout(timer, 3000);
});
}, 3000);
function someLongProcess(duration, callback) {
setTimeout(function() {
console.log('long process: ' + duration);
callback();
}, duration);
}
/* getRandomInt(min, max) {} */
setTimeout
data = this.appendAnalyticsData(data);
data = this.appendSubmissionData(data);
data = this.appendAdditionalInputs(data);
data = this.pruneObject(data);
1) Nested Function Calls
data = this.pruneObject(
this.appendAdditionalInputs(
this.appendSubmissionData(
this.appendAnalyticsData(data)
)
)
);
2) forEach
var funcs = [
this.appendAnalyticsData,
this.appendSubmissionData,
this.appendAdditionalInputs,
this.pruneObject
];
funcs.forEach(function(func) {
data = func(data);
});
3) reduce
var funcs = [
this.appendAnalyticsData,
this.appendSubmissionData,
this.appendAdditionalInputs,
this.pruneObject
];
data = funcs.reduce(function(memo, func) {
return func(memo);
}, data);
4) flow
data = _.flow(
this.appendAnalyticsData,
this.appendSubmissionData,
this.appendAdditionalInputs,
this.pruneObject
)(data);
eslint-plugin-smells
function ShoppingCart() { this.items = []; }
ShoppingCart.prototype.addItem = function(item) {
this.items.push(item);
};
function Product(name) { this.name = name; }
Product.prototype.addToCart = function() {
shoppingCart.addItem(this);
};
var shoppingCart = new ShoppingCart();
var product = new Product('Socks');
product.addToCart();
console.log(shoppingCart.items);
function ShoppingCart() { this.items = []; }
ShoppingCart.prototype.addItem = function(item) {
this.items.push(item);
};
function Product(name) { this.name = name; }
Product.prototype.addToCart = function() {
shoppingCart.addItem(this); // 8: Inappropriate
};
var shoppingCart = new ShoppingCart();
var product = new Product('Socks');
product.addToCart();
console.log(shoppingCart.items);
function ShoppingCart() { this.items = []; }
ShoppingCart.prototype.addItem = function(item) {
this.items.push(item);
};
function Product(name, shoppingCart) { // 6: Accept Dependency
this.name = name;
this.shoppingCart = shoppingCart; // 8: Save off Dependency
}
Product.prototype.addToCart = function() {
this.shoppingCart.addItem(this);
};
var shoppingCart = new ShoppingCart();
var product = new Product('Socks', shoppingCart); // 15: Pass in Dependency
product.addToCart();
console.log(shoppingCart.items);
var channel = postal.channel(); // 1: Broker
function ShoppingCart() {
this.items = [];
channel.subscribe('shoppingcart.add', this.addItem); // 5: Listen to Message
}
ShoppingCart.prototype.addItem = function(item) {
this.items.push(item);
};
function Product(name) { this.name = name; }
Product.prototype.addToCart = function() {
channel.publish('shoppingcart.add', this); // 13: Publish Message
};
var shoppingCart = new ShoppingCart();
var product = new Product('Socks');
product.addToCart();
console.log(shoppingCart.items);
postal
by @ifandelsevar search = document.querySelector('.Autocomplete');
search.addEventListener('input', function(e) {
// Make Ajax call for autocomplete
console.log(e.target.value);
});
var search = document.querySelector('.Autocomplete');
search.addEventListener('input', _.throttle(function(e) {
// Make Ajax call for autocomplete
console.log(e.target.value);
}, 500));
var search = document.querySelector('.Autocomplete');
search.addEventListener('input', _.debounce(function(e) {
// Make Ajax call for autocomplete
console.log(e.target.value);
}, 500));
var search = document.querySelector('.Autocomplete');
search.addEventListener('input', _.debounce(function(e) {
// Make Ajax call for autocomplete
console.log(e.target.value);
}, 500));
var search = document.querySelector('.Autocomplete');
search.addEventListener('input', function(e) {
console.log(e.target.value);
});
var search = document.querySelector('.Autocomplete');
search.addEventListener('input', function(e) {
console.log(e.target.value);
});
var search = document.querySelector('.Autocomplete');
search.addEventListener('input', function matches(e) {
console.log(e.target.value);
});
debounce
var search = document.querySelector('.Autocomplete');
search.addEventListener('input', _.debounce(function matches(e) {
console.log(e.target.value);
}, 500));
debounce
(Heavy Bottom Up)var search = document.querySelector('.Autocomplete');
search.addEventListener('input', _.debounce(function matches(e) {
console.log(e.target.value);
}, 500));
One-time Event Handler
document.querySelector('button')
.addEventListener('click', function handler() {
alert('Ka-boom!');
this.removeEventListener('click', handler);
});
var kaboom = function() { alert('Ka-boom'); };
document.querySelector('button').addEventListener('click', kaboom);
document.querySelector('#egg').addEventListener('mouseenter', kaboom);
$(document).ready(function() {
// wire up event handlers
// declare all the things
// etc...
});
(function(myApp) {
myApp.init = function() {
// kick off your code
};
myApp.handleClick = function() {}; // etc...
}(window.myApp = window.myApp || {}));
// Only include at end of main application...
$(document).ready(function() {
window.myApp.init();
});
var Application = (function() {
function Application() {
// kick off your code
}
Application.prototype.handleClick = function() {};
return Application;
}());
// Only include at end of main application...
$(document).ready(function() {
new Application();
});
"...you can set the directionality of it to be 2-Way Data Binding. That actually seems to be a good idea until you have a large scale application and then it turns out you have no idea whats going on... and turns out to be an anti-pattern for large apps." --Misko Hevery https://www.youtube.com/watch?v=uD6Okha_Yj0#t=1785
http://elijahmanor.com @elijahmanor http://bit.ly/js-smells
console.log('code');