Integrated static type-checking via JSDoc and Flow

This commit is contained in:
squidfunk 2017-02-25 17:13:50 +01:00
parent 672a39b697
commit 22a2f084a1
22 changed files with 114 additions and 83 deletions

View File

@ -45,17 +45,25 @@ git checkout -- .
FILES=$(git diff --cached --name-only --diff-filter=ACMR | \
grep "\.\(js\|jsx\|scss\)$")
# Run the check and print indicator
# Run check and print indicator
if [ "$FILES" ]; then
npm run lint --silent
# If we're on master, abort commit
# If linter terminated with errors, abort commit
if [ $? -gt 0 ]; then
echo -e "\x1B[31m✗\x1B[0m Linter - \x1B[31m$MESSAGE\x1B[0m"
exit 1
else
echo -e "\x1B[32m✓\x1B[0m Linter"
fi
# If flow terminated with errors, abort commit
npm run flow --silent
if [ $? -gt 0 ]; then
echo -e "\x1B[31m✗\x1B[0m Flow - \x1B[31m$MESSAGE\x1B[0m"
exit 1
else
echo -e "\x1B[32m✓\x1B[0m Flow"
fi
fi
# We're good

View File

@ -26,9 +26,9 @@
declare module "fastclick" {
/* FastClick type */
/* Type: FastClick */
declare type FastClick = {
attach(name: HTMLElement): null
attach(name: HTMLElement): void
}
/* Exports */

View File

@ -26,13 +26,13 @@
declare module "js-cookie" {
/* Options type for setting cookie values */
/* Type: Options for setting cookie values */
declare type Options = {
path?: string,
expires?: number | string
}
/* Cookie type */
/* Type: Cookie */
declare type Cookie = {
getJSON(json: string): Object,
set(key: string, value: string, options?: Options): string

View File

@ -24,9 +24,11 @@
* Declarations
* ------------------------------------------------------------------------- */
/*
* Currently, it's not possible to export a function that returns a class type,
* as the imports just don't correctly work with flow. As a workaround we
* export an object until this error is fixed.
*/
declare module "lunr" {
declare class lunr {
// TODO
}
declare function exports(): lunr
declare function exports(name: () => void): Object
}

View File

@ -25,7 +25,7 @@
* ------------------------------------------------------------------------- */
declare class Modernizr {
static addTest(name: string, test: () => boolean): null
static addTest(name: string, test: () => boolean): void
}
/* Exports */

View File

@ -30,7 +30,7 @@ export default /* Jsx */ {
* Create a native DOM node from JSX's intermediate representation
*
* @param {string} tag - Tag name
* @param {Object} properties - Properties // TODO: nullable, as the second...
* @param {?Object} properties - Properties
* @param {...(string|number|Array)} children - Child nodes
* @return {HTMLElement} Native DOM node
*/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -124,7 +124,7 @@
{% endblock %}
</div>
{% block scripts %}
<script src="{{ base_url }}/assets/javascripts/application-e546393d04.js"></script>
<script src="{{ base_url }}/assets/javascripts/application-3fa7d77989.js"></script>
<script>app.initialize({url:{base:"{{ base_url }}"}})</script>
{% for path in extra_javascript %}
<script src="{{ path }}"></script>

View File

@ -3,7 +3,7 @@
<div class="md-search__overlay"></div>
<div class="md-search__inner">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" placeholder="{{ lang.t('search.placeholder') }}" accesskey="s" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false">
<input type="text" class="md-search__input" name="query" placeholder="{{ lang.t('search.placeholder') }}" accesskey="s" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="query">
<label class="md-icon md-search__icon" for="search"></label>
</form>
<div class="md-search__output">

View File

@ -96,14 +96,6 @@
"chai": "^3.5.0",
"eslint-plugin-mocha": "^4.8.0",
"gemini": "^4.14.3",
"karma": "^1.3.0",
"karma-chrome-launcher": "^2.0.0",
"karma-coverage": "^1.1.1",
"karma-mocha": "^1.3.0",
"karma-notify-reporter": "^1.0.1",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "0.0.26",
"karma-webpack": "^2.0.1",
"mocha": "^3.2.0",
"moniker": "^0.1.2",
"saucelabs": "^1.4.0",

View File

@ -4,7 +4,7 @@
],
"plugins": [
["transform-react-jsx", {
"pragma": "JSX.createElement"
"pragma": "Jsx.createElement"
}]
]
}

View File

@ -28,26 +28,25 @@ import Material from "./components/Material"
* ------------------------------------------------------------------------- */
/**
* [initialize description]
* Initialize Material for MkDocs
*
* @param {Object} config - TODO // TODO: define via type declaration!?
* @param {Object} config - Configuration
*/
export const initialize = config => {
function initialize(config) { // eslint-disable-line func-style
/* Initialize Modernizr and FastClick */
new Material.Event.Listener(document, "DOMContentLoaded", () => {
/* Test for iOS */
Modernizr.addTest("ios", () => {
return !!navigator.userAgent.match(/(iPad|iPhone|iPod)/g)
})
if (!(document.body instanceof HTMLElement))
throw new ReferenceError
/* Attach FastClick to mitigate 300ms delay on touch devices */
FastClick.attach(document.body)
/* Test for iOS */
Modernizr.addTest("ios", () => {
return !!navigator.userAgent.match(/(iPad|iPhone|iPod)/g)
})
/* Wrap all data tables for better overflow scrolling */
const tables = document.querySelectorAll("table:not([class])")
Array.prototype.forEach.call(tables, table => {
@ -119,7 +118,7 @@ export const initialize = config => {
new Material.Search.Lock("[data-md-toggle=search]")))
/* Component: search results */
new Material.Event.Listener(document.forms.search.query, [
new Material.Event.Listener("[data-md-component=query]", [
"focus", "keyup"
], new Material.Search.Result("[data-md-component=result]", () => {
return fetch(`${config.url.base}/mkdocs/search_index.json`, {
@ -143,7 +142,9 @@ export const initialize = config => {
new Material.Event.Listener("[data-md-component=navigation] [href^='#']",
"click", () => {
const toggle = document.querySelector("[data-md-toggle=drawer]")
if (toggle instanceof HTMLInputElement && toggle.checked) {
if (!(toggle instanceof HTMLInputElement))
throw new ReferenceError
if (toggle.checked) {
toggle.checked = false
toggle.dispatchEvent(new CustomEvent("change"))
}
@ -152,17 +153,24 @@ export const initialize = config => {
/* Listener: focus input after opening search */
new Material.Event.Listener("[data-md-toggle=search]", "change", ev => {
setTimeout(toggle => {
const query = document.forms.search.query
if (toggle instanceof HTMLInputElement && toggle.checked)
if (!(toggle instanceof HTMLInputElement))
throw new ReferenceError
if (toggle.checked) {
const query = document.querySelector("[data-md-component=query]")
if (!(query instanceof HTMLInputElement))
throw new ReferenceError
query.focus()
}
}, 400, ev.target)
}).listen()
/* Listener: open search on focus */
new Material.Event.MatchMedia("(min-width: 960px)",
new Material.Event.Listener(document.forms.search.query, "focus", () => {
new Material.Event.Listener("[data-md-component=query]", "focus", () => {
const toggle = document.querySelector("[data-md-toggle=search]")
if (toggle instanceof HTMLInputElement && !toggle.checked) {
if (!(toggle instanceof HTMLInputElement))
throw new ReferenceError
if (!toggle.checked) {
toggle.checked = true
toggle.dispatchEvent(new CustomEvent("change"))
}
@ -172,7 +180,9 @@ export const initialize = config => {
new Material.Event.MatchMedia("(min-width: 960px)",
new Material.Event.Listener(document.body, "click", () => {
const toggle = document.querySelector("[data-md-toggle=search]")
if (toggle instanceof HTMLInputElement && toggle.checked) {
if (!(toggle instanceof HTMLInputElement))
throw new ReferenceError
if (toggle.checked) {
toggle.checked = false
toggle.dispatchEvent(new CustomEvent("change"))
}
@ -183,10 +193,15 @@ export const initialize = config => {
const code = ev.keyCode || ev.which
if (code === 27) {
const toggle = document.querySelector("[data-md-toggle=search]")
if (toggle instanceof HTMLInputElement && toggle.checked) {
if (!(toggle instanceof HTMLInputElement))
throw new ReferenceError
if (toggle.checked) {
toggle.checked = false
toggle.dispatchEvent(new CustomEvent("change"))
document.forms.search.query.blur()
const query = document.querySelector("[data-md-component=query]")
if (!(query instanceof HTMLInputElement))
throw new ReferenceError
query.focus()
}
}
}).listen()
@ -213,7 +228,7 @@ export const initialize = config => {
default: return Promise.resolve([])
}
/* Render repository source information */
/* Render repository information */
})().then(facts => {
const sources = document.querySelectorAll("[data-md-source]")
Array.prototype.forEach.call(sources, source => {
@ -222,3 +237,11 @@ export const initialize = config => {
})
})
}
/* ----------------------------------------------------------------------------
* Exports
* ------------------------------------------------------------------------- */
export {
initialize
}

View File

@ -31,7 +31,7 @@ export default class Scrolling {
*
* @constructor
*
* @property {HTMLElement} el_ - Navigation
* @property {HTMLElement} el_ - Primary navigation
*
* @param {(string|HTMLElement)} el - Selector or HTML element
*/
@ -55,7 +55,9 @@ export default class Scrolling {
/* Find all toggles and check which one is active */
const toggles = this.el_.querySelectorAll("[data-md-toggle]")
Array.prototype.forEach.call(toggles, toggle => {
if (toggle instanceof HTMLInputElement && toggle.checked) {
if (!(toggle instanceof HTMLInputElement))
throw new ReferenceError
if (toggle.checked) {
/* Find corresponding navigational pane */
let pane = toggle.nextElementSibling
@ -144,7 +146,9 @@ export default class Scrolling {
/* Find all toggles and check which one is active */
const toggles = this.el_.querySelectorAll("[data-md-toggle]")
Array.prototype.forEach.call(toggles, toggle => {
if (toggle instanceof HTMLInputElement && toggle.checked) {
if (!(toggle instanceof HTMLInputElement))
throw new ReferenceError
if (toggle.checked) {
/* Find corresponding navigational pane */
let pane = toggle.nextElementSibling

View File

@ -31,9 +31,9 @@ export default class Lock {
*
* @constructor
*
* @property {HTMLInputElement} el_ - TODO
* @property {HTMLElement} lock_ - Element to lock
* @property {number} offset_ - TODO
* @property {HTMLInputElement} el_ - Lock toggle
* @property {HTMLElement} lock_ - Element to lock (document body)
* @property {number} offset_ - Current page y-offset
*
* @param {(string|HTMLElement)} el - Selector or HTML element
*/

View File

@ -33,14 +33,15 @@ export default class Result {
*
* @constructor
*
* @property {HTMLElement} el_ - TODO
* @property {(Object|Array<Object>|Function)} data_ - TODO (very dirty)
* @property {*} meta_ - // TODO (must be done like this, as React$Component does not return the correct thing) (React$Element<*>|Element)
* @property {*} list_ - // TODO (must be done like this, as React$Component does not return the correct thing)
* @property {Object} index_ - TODO
* @property {HTMLElement} el_ - Search result container
* @property {(Array<Object>|Function)} data_ - Raw document data
* @property {Object} docs_ - Indexed documents
* @property {HTMLElement} meta_ - Search meta information
* @property {HTMLElement} list_ - Search result list
* @property {Object} index_ - Search index
*
* @param {(string|HTMLElement)} el - Selector or HTML element
* @param {(Array<Object>|Function)} data - Promise or array providing data // TODO ????
* @param {(Array<Object>|Function)} data - Function providing data or array
*/
constructor(el, data) {
const ref = (typeof el === "string")
@ -73,9 +74,9 @@ export default class Result {
* would be better to create something more intelligent, highlighting the
* search occurrences and making a better summary out of it
*
* @param {string} string - TODO
* @param {number} n - TODO
* @return {string} TODO
* @param {string} string - String to be truncated
* @param {number} n - Number of characters
* @return {string} Truncated string
*/
truncate_(string, n) {
let i = n
@ -107,7 +108,7 @@ export default class Result {
})
/* Index documents */
this.data_ = data.reduce((docs, doc) => {
this.docs_ = data.reduce((docs, doc) => {
this.index_.add(doc)
docs[doc.location] = doc
return docs
@ -132,10 +133,9 @@ export default class Result {
this.list_.removeChild(this.list_.firstChild)
/* Perform search on index and render documents */
let result = this.index_.search(target.value)
result += 3
const result = this.index_.search(target.value)
result.forEach(item => {
const doc = this.data_[item.ref]
const doc = this.docs_[item.ref]
/* Check if it's a anchor link on the current page */
let [pathname] = doc.location.split("#")
@ -166,7 +166,9 @@ export default class Result {
Array.prototype.forEach.call(anchors, anchor => {
anchor.addEventListener("click", ev2 => {
const toggle = document.querySelector("[data-md-toggle=search]")
if (toggle instanceof HTMLInputElement && toggle.checked) {
if (!(toggle instanceof HTMLInputElement))
throw new ReferenceError
if (toggle.checked) {
toggle.checked = false
toggle.dispatchEvent(new CustomEvent("change"))
}

View File

@ -31,10 +31,10 @@ export default class Position {
*
* @constructor
*
* @property {HTMLElement} el_ - TODO
* @property {HTMLElement} parent_ - TODO
* @property {number} height_ - TODO
* @property {number} offset_ - TODO
* @property {HTMLElement} el_ - Sidebar
* @property {HTMLElement} parent_ - Sidebar container
* @property {number} height_ - Current sidebar height
* @property {number} offset_ - Current page y-offset
*
* @param {(string|HTMLElement)} el - Selector or HTML element
*/

View File

@ -29,13 +29,13 @@ import Cookies from "js-cookie"
export default class Abstract {
/**
* Retrieve source information
* Retrieve repository information
*
* @constructor
*
* @property {HTMLAnchorElement} el_ - TODO
* @property {string} base_ - TODO
* @property {number} salt_ - TODO
* @property {HTMLAnchorElement} el_ - Link to repository
* @property {string} base_ - API base URL
* @property {number} salt_ - Unique identifier
*
* @param {(string|HTMLAnchorElement)} el - Selector or HTML element
*/
@ -56,7 +56,7 @@ export default class Abstract {
/**
* Retrieve data from Cookie or fetch from respective API
*
* @return {Promise<*>} Promise that returns an array of facts // TODO: @returns {Promise.<string, Error>}
* @return {Promise<Array<string>>} Promise that returns an array of facts
*/
fetch() {
return new Promise(resolve => {

View File

@ -29,7 +29,7 @@ import Abstract from "./Abstract"
export default class GitHub extends Abstract {
/**
* Retrieve source information from GitHub
* Retrieve repository information from GitHub
*
* @constructor
* @param {(string|HTMLAnchorElement)} el - Selector or HTML element
@ -42,9 +42,9 @@ export default class GitHub extends Abstract {
}
/**
* Fetch relevant source information from GitHub
* Fetch relevant repository information from GitHub
*
* @return {Promise<*>} Promise returning an array of facts
* @return {Promise<Array<string>>} Promise returning an array of facts
*/
fetch_() {
return fetch(this.base_)

View File

@ -31,7 +31,7 @@ export default class Repository {
*
* @constructor
*
* @property {HTMLElement} el_ - TODO
* @property {HTMLElement} el_ - Repository information
*
* @param {(string|HTMLElement)} el - Selector or HTML element
*/
@ -45,12 +45,12 @@ export default class Repository {
}
/**
* Initialize the source repository
* Initialize the repository
*
* @param {Array<string>} facts - Facts to be rendered
*/
initialize(facts) {
if (facts.length)
if (facts.length && this.el_.children.length)
this.el_.children[this.el_.children.length - 1].appendChild(
<ul class="md-source__facts">
{facts.map(fact => <li class="md-source__fact">{fact}</li>)}

View File

@ -64,7 +64,7 @@
line-height: 1.2;
white-space: nowrap;
// Hovered source information
// Hovered source container
&:hover {
opacity: 0.7;
}

View File

@ -30,7 +30,7 @@
<input type="text" class="md-search__input" name="query"
placeholder="{{ lang.t('search.placeholder') }}"
accesskey="s" autocapitalize="off" autocorrect="off"
autocomplete="off" spellcheck="false" />
autocomplete="off" spellcheck="false" data-md-component="query" />
<label class="md-icon md-search__icon" for="search"></label>
</form>
<div class="md-search__output">