Created
November 10, 2016 09:59
-
-
Save screeny05/b1dff3728caf369dbf37f10faa164718 to your computer and use it in GitHub Desktop.
BEM / ITCSS Linter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function bemLinter(){ | |
const namespaces = ['o', 'c', 'u', 's', 't', 'is', 'has']; | |
const suffixes = ['xs', 's', 'ms', 'sm', 'md', 'lg', 'l', 'xl', 'print']; | |
const SEVERITY_ERROR = 'error'; | |
const SEVERITY_WARNING = 'warn'; | |
const SEVERITY_INFO = 'info'; | |
const ERR_TYPE_STRAY_ELEMENT = 'stray-element'; | |
const ERR_TYPE_MISSING_NAMESPACE = 'missing-namespace'; | |
const ERR_TYPE_INVALID_SUFFIX = 'invalid-suffix'; | |
const ERR_TYPE_CAMELCASE = 'camelcase'; | |
const ERR_TYPE_MISSING_NON_MODIFIED = 'missing-non-modified'; | |
/** cls has to have a namespace [deactivated - only for itcss] */ | |
const namespaceChecker = (node, cls) => { | |
const regex = new RegExp(`^_?(${namespaces.join('|')})-.`); | |
return { | |
success: regex.test(cls), | |
message: `${cls} needs to start with a namespace. possible namespaces are: ${namespaces.join(', ')}`, | |
severity: SEVERITY_ERROR, | |
type: ERR_TYPE_MISSING_NAMESPACE | |
}; | |
}; | |
/** cls either needs no suffix, or a valid one */ | |
const mediaSuffixChecker = (node, cls) => { | |
const regex = /@(.*?)$/; | |
const match = cls.match(regex); | |
if(!match){ | |
return { success: true }; | |
} | |
return { | |
success: suffixes.indexOf(match[1]) !== -1, | |
message: `${cls} has an invalid suffix of '${match[1]}'. possible suffixes are: ${suffixes.join(', ')}`, | |
severity: SEVERITY_ERROR, | |
type: ERR_TYPE_INVALID_SUFFIX | |
} | |
}; | |
/** cls has to be all lowercase */ | |
const camelcaseChecker = (node, cls) => { | |
return { | |
success: cls.toLowerCase() === cls, | |
message: `${cls} needs to be all-lowercase. no camleCase allowed`, | |
severity: SEVERITY_ERROR, | |
type: ERR_TYPE_CAMELCASE | |
} | |
}; | |
/** modifiers can't stand alone */ | |
const nonModifiedChecker = (node, cls) => { | |
const regex = /^(.*?)--/; | |
const match = cls.match(regex); | |
if(!match){ | |
return { success: true }; | |
} | |
return { | |
success: node.classList.contains(match[1]), | |
message: `the modifier ${cls} doesn't have a matching block or element on the same element ${match[1]}`, | |
severity: SEVERITY_ERROR, | |
type: ERR_TYPE_MISSING_NON_MODIFIED | |
}; | |
}; | |
/** element has to be inside a block */ | |
const strayElementChecker = (node, cls) => { | |
const regex = /^(.*?)__/; | |
const match = cls.match(regex); | |
if(!match){ | |
return { success: true }; | |
} | |
while(node.parentElement){ | |
node = node.parentElement; | |
if(node.classList.contains(match[1])){ | |
return { success: true }; | |
} | |
} | |
return { | |
success: false, | |
message: `the element ${cls} is not contained within the parent-block ${match[1]}`, | |
severity: SEVERITY_ERROR, | |
type: ERR_TYPE_STRAY_ELEMENT | |
}; | |
}; | |
/** prints messages as collapsed console-entries */ | |
const printMessages = messages => { | |
let lastType = null; | |
messages | |
.sort((a, b) => a.type < b.type ? -1 : a.type > b.type ? 1 : 0) | |
.forEach(msg => { | |
if(msg.type !== lastType){ | |
console.groupEnd(); | |
console.groupCollapsed(msg.type); | |
lastType = msg.type; | |
} | |
console[msg.severity](msg.message, msg.node); | |
}); | |
console.groupEnd(); | |
}; | |
const checkers = [ | |
// namespaceChecker | |
mediaSuffixChecker, | |
camelcaseChecker, | |
nonModifiedChecker, | |
strayElementChecker | |
]; | |
const messages = []; | |
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT); | |
while(walker.nextNode()){ | |
const node = walker.currentNode; | |
if(node.classList.length === 0){ | |
continue; | |
} | |
node.classList.forEach(cls => { | |
checkers.forEach(checker => { | |
const message = checker(node, cls); | |
if(message.success){ | |
return; | |
} | |
messages.push({ | |
node, | |
message: message.message, | |
severity: message.severity, | |
type: message.type | |
}); | |
}); | |
}); | |
} | |
printMessages(messages); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment