diff --git a/mode/javascript/flow.html b/mode/javascript/flow.html
new file mode 100644
index 0000000000..6972c3833a
--- /dev/null
+++ b/mode/javascript/flow.html
@@ -0,0 +1,62 @@
+
+
+
CodeMirror: Flow mode
+
+
+
+
+
+
+
+
+
+
+
+Flow mode
+
+
+
+
+
+
+ This is a specialization of the JavaScript mode.
+
diff --git a/mode/javascript/index.html b/mode/javascript/index.html
index 4eff2e28bc..cfeda96f4e 100644
--- a/mode/javascript/index.html
+++ b/mode/javascript/index.html
@@ -99,6 +99,9 @@ JavaScript mode
typescript
which will activate additional
syntax highlighting and some other things for TypeScript code
(demo).
+ flow
which will activate additional
+ syntax highlighting and some other things for Flow code
+ (demo).
statementIndent
which (given a number) will
determine the amount of indentation to use for statements
continued on a new line.
@@ -110,5 +113,5 @@ JavaScript mode
- MIME types defined: text/javascript
, application/json
, application/ld+json
, text/typescript
, application/typescript
.
+ MIME types defined: text/javascript
, application/json
, application/ld+json
, text/typescript
, application/typescript
, text/flow
, application/flow
.
diff --git a/mode/javascript/javascript.js b/mode/javascript/javascript.js
index 7e7b3879ab..9a33b69e71 100644
--- a/mode/javascript/javascript.js
+++ b/mode/javascript/javascript.js
@@ -17,6 +17,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
var jsonldMode = parserConfig.jsonld;
var jsonMode = parserConfig.json || jsonldMode;
var isTS = parserConfig.typescript;
+ var isFlow = parserConfig.flow;
var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
// Tokenizer
@@ -75,6 +76,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
return ret(ch);
} else if (ch == "=" && stream.eat(">")) {
return ret("=>", "operator");
+ } else if (isFlow && (ch == '%' && stream.match(/checks/))) {
+ return ret('%checks', 'operator')
} else if (ch == "0" && stream.match(/^(?:x[\da-f]+|o[0-7]+|b[01]+)n?/i)) {
return ret("number", "number");
} else if (/\d/.test(ch)) {
@@ -179,7 +182,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
var arrow = stream.string.indexOf("=>", stream.start);
if (arrow < 0) return;
- if (isTS) { // Try to skip TypeScript return type declarations after the arguments
+ if (isTS || isFlow) { // Try to skip TypeScript return type declarations after the arguments
var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow))
if (m) arrow = m.index
}
@@ -365,19 +368,21 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
}
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
- if (type == "class" || (isTS && value == "interface")) {
+ if (type == "class" || ((isTS || isFlow) && value == "interface")) {
cx.marked = "keyword"
return cont(pushlex("form", type == "class" ? type : value), className, poplex)
}
if (type == "variable") {
- if (isTS && value == "declare") {
+ if ((isTS || isFlow) && value == "declare") {
cx.marked = "keyword"
return cont(statement)
- } else if (isTS && (value == "module" || value == "enum" || value == "type") && cx.stream.match(/^\s*\w/, false)) {
+ } else if ((isTS || isFlow) && (value == "module" || value == "type") && cx.stream.match(/^\s*\w/, false)) {
cx.marked = "keyword"
- if (value == "enum") return cont(enumdef);
- else if (value == "type") return cont(typename, expect("operator"), typeexpr, expect(";"));
+ if (value == "type") return cont(typename, expect("operator"), typeexpr, expect(";"));
else return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex)
+ } else if (isTS && value == "enum" && cx.stream.match(/^\s*\w/, false)) {
+ cx.marked = "keyword"
+ return cont(enumdef)
} else if (isTS && value == "namespace") {
cx.marked = "keyword"
return cont(pushlex("form"), expression, statement, poplex)
@@ -422,9 +427,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
if (type == "function") return cont(functiondef, maybeop);
- if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); }
+ if (type == "class" || ((isTS || isFlow) && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); }
if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression);
- if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
+ if (type == "(") return cont(pushlex(")"), maybeexpression, maybetypeannotation, expect(")"), poplex, maybeop);
if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
if (type == "{") return contCommasep(objprop, "}", null, maybeop);
@@ -433,6 +438,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "import") return cont(expression);
return cont();
}
+ function maybetypeannotation(type) {
+ if (isFlow && type === ":") {
+ return cont(typeexpr);
+ }
+ return pass();
+ }
function maybeexpression(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expression);
@@ -448,7 +459,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
if (type == "operator") {
if (/\+\+|--/.test(value) || isTS && value == "!") return cont(me);
- if (isTS && value == "<" && cx.stream.match(/^([^>]|<.*?>)*>\s*\(/, false))
+ if ((isTS || isFlow) && value == "<" && cx.stream.match(/^([^>]|<.*?>)*>\s*\(/, false))
return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, me);
if (value == "?") return cont(expression, expect(":"), expr);
return cont(expr);
@@ -488,7 +499,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
function maybeTarget(noComma) {
return function(type) {
if (type == ".") return cont(noComma ? targetNoComma : target);
- else if (type == "variable" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma)
+ else if (type == "variable" && (isTS || isFlow)) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma)
else return pass(noComma ? expressionNoComma : expression);
};
}
@@ -513,7 +524,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
cx.marked = "property";
if (value == "get" || value == "set") return cont(getterSetter);
var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params
- if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\s*:\s*/, false)))
+ if ((isTS || isFlow) && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\s*:\s*/, false)))
cx.state.fatArrowAt = cx.stream.pos + m[0].length
return cont(afterprop);
} else if (type == "number" || type == "string") {
@@ -573,18 +584,18 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
return pass(statement, block);
}
function maybetype(type, value) {
- if (isTS) {
+ if ((isTS || isFlow)) {
if (type == ":") return cont(typeexpr);
if (value == "?") return cont(maybetype);
}
}
function maybetypeOrIn(type, value) {
- if (isTS && (type == ":" || value == "in")) return cont(typeexpr)
+ if ((isTS || isFlow) && (type == ":" || value == "in")) return cont(typeexpr)
}
function mayberettype(type) {
- if (isTS && type == ":") {
+ if ((isTS || isFlow) && type == ":") {
if (cx.stream.match(/^\s*\w+\s+is\b/, false)) return cont(expression, isKW, typeexpr)
- else return cont(typeexpr)
+ else return cont(typeexpr, maybeChecks)
}
}
function isKW(_, value) {
@@ -593,10 +604,26 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
return cont()
}
}
+ function maybeChecks(type) {
+ if (isFlow && type === '%checks') return cont()
+ }
+ function maybeExactType(_, value) {
+ if (isFlow && value == "|")
+ return cont(pushlex("|}"), commasep(typeprop, "|}", ",;"))
+ else
+ return pass(pushlex("}"), commasep(typeprop, "}", ",;"))
+ }
function typeexpr(type, value) {
- if (value == "keyof" || value == "typeof" || value == "infer") {
+ if (isTS && (value == "keyof" || value == "infer")) {
cx.marked = "keyword"
- return cont(value == "typeof" ? expressionNoComma : typeexpr)
+ return cont(typeexpr)
+ }
+ if (isFlow && value == "?") {
+ cx.marked = "keyword"
+ return cont(typeexpr)
+ }
+ if (value == "typeof") {
+ return cont(expressionNoComma)
}
if (type == "variable" || value == "void") {
cx.marked = "type"
@@ -605,7 +632,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (value == "|" || value == "&") return cont(typeexpr)
if (type == "string" || type == "number" || type == "atom") return cont(afterType);
if (type == "[") return cont(pushlex("]"), commasep(typeexpr, "]", ","), poplex, afterType)
- if (type == "{") return cont(pushlex("}"), commasep(typeprop, "}", ",;"), poplex, afterType)
+ if (type == "{") return cont(maybeExactType, poplex, afterType)
if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType, afterType)
if (type == "<") return cont(commasep(typeexpr, ">"), typeexpr)
}
@@ -613,11 +640,16 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "=>") return cont(typeexpr)
}
function typeprop(type, value) {
- if (type == "variable" || cx.style == "keyword") {
+ if (isTS && isModifier(value)) {
+ cx.marked = "keyword";
+ return cont(typeprop);
+ } else if (type == "variable" || cx.style == "keyword") {
cx.marked = "property"
return cont(typeprop)
} else if (value == "?" || type == "number" || type == "string") {
return cont(typeprop)
+ } else if (isFlow && (value === "+" || value === "-")) {
+ return cont(typeprop)
} else if (type == ":") {
return cont(typeexpr)
} else if (type == "[") {
@@ -643,7 +675,13 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
}
function typeparam() {
- return pass(typeexpr, maybeTypeDefault)
+ return pass(typeexpr, maybeBound, maybeTypeDefault)
+ }
+ function maybeBound(type, value) {
+ if (isFlow && type == ':') {
+ cx.marked = 'operator'
+ return cont(typeexpr)
+ }
}
function maybeTypeDefault(_, value) {
if (value == "=") return cont(typeexpr)
@@ -701,13 +739,13 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, statement, popcontext);
- if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondef)
+ if ((isTS || isFlow) && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondef)
}
function functiondecl(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondecl);}
if (type == "variable") {register(value); return cont(functiondecl);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, popcontext);
- if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondecl)
+ if ((isTS || isFlow) && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondecl)
}
function typename(type, value) {
if (type == "keyword" || type == "variable") {
@@ -734,9 +772,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
}
function classNameAfter(type, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, classNameAfter)
- if (value == "extends" || value == "implements" || (isTS && type == ",")) {
+ if (value == "extends" || value == "implements" || ((isTS || isFlow) && type == ",")) {
if (value == "implements") cx.marked = "keyword";
- return cont(isTS ? typeexpr : expression, classNameAfter);
+ return cont((isTS || isFlow) ? typeexpr : expression, classNameAfter);
}
if (type == "{") return cont(pushlex("}"), classBody, poplex);
}
@@ -748,18 +786,21 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
cx.marked = "keyword";
return cont(classBody);
}
+ if (value === '+' || value === '-') {
+ return cont(classBody);
+ }
if (type == "variable" || cx.style == "keyword") {
cx.marked = "property";
- return cont(isTS ? classfield : functiondef, classBody);
+ return cont((isTS || isFlow) ? classfield : functiondef, classBody);
}
- if (type == "number" || type == "string") return cont(isTS ? classfield : functiondef, classBody);
+ if (type == "number" || type == "string") return cont((isTS || isFlow) ? classfield : functiondef, classBody);
if (type == "[")
- return cont(expression, maybetype, expect("]"), isTS ? classfield : functiondef, classBody)
+ return cont(expression, maybetype, expect("]"), (isTS || isFlow) ? classfield : functiondef, classBody)
if (value == "*") {
cx.marked = "keyword";
return cont(classBody);
}
- if (isTS && type == "(") return pass(functiondecl, classBody)
+ if ((isTS || isFlow) && type == "(") return pass(functiondecl, classBody)
if (type == ";" || type == ",") return cont(classBody);
if (type == "}") return cont();
if (value == "@") return cont(expression, classBody)
@@ -919,5 +960,7 @@ CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true});
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
+CodeMirror.defineMIME("text/flow", { name: "javascript", flow: true });
+CodeMirror.defineMIME("application/flow", { name: "javascript", flow: true });
});
diff --git a/mode/javascript/test.js b/mode/javascript/test.js
index 04faeafa31..0b455c3872 100644
--- a/mode/javascript/test.js
+++ b/mode/javascript/test.js
@@ -405,6 +405,10 @@
"[keyword function] [def x][operator <][type T] [keyword extends] [keyword keyof] [type X][operator >]([def a]: [type T]) {",
" [keyword return]")
+ TS("typescript_typeof",
+ "[keyword function] [def x][operator <][type T] [keyword extends] [keyword typeof] [variable X][operator >]([def a]: [type T]) {",
+ " [keyword return]")
+
TS("typescript_new_typeargs",
"[keyword let] [def x] [operator =] [keyword new] [variable Map][operator <][type string], [type Date][operator >]([string-2 `foo${][variable bar][string-2 }`])")
@@ -454,6 +458,160 @@
" [property bar]: [type void]",
"}")
+ var flow_mode = CodeMirror.getMode({indentUnit: 2}, "application/flow")
+ function F(name) {
+ test.mode(name, flow_mode, Array.prototype.slice.call(arguments, 1))
+ }
+
+ F("flow_extend_type",
+ "[keyword class] [def Foo] [keyword extends] [type Some][operator <][type Type][operator >] {}")
+
+ F("flow_arrow_type",
+ "[keyword let] [def x]: ([variable arg]: [type Type]) [operator =>] [type ReturnType]")
+
+ F("flow_class",
+ "[keyword class] [def Foo] {",
+ " [keyword static] [property main]() {}",
+ " [property _foo]: [type string];",
+ "}")
+
+ F("flow_literal_types",
+ "[keyword import] [keyword *] [keyword as] [def Sequelize] [keyword from] [string 'sequelize'];",
+ "[keyword interface] [def MyAttributes] {",
+ " [property truthy]: [string 'true'] [operator |] [number 1] [operator |] [atom true];",
+ " [property falsy]: [string 'false'] [operator |] [number 0] [operator |] [atom false];",
+ "}",
+ "[keyword interface] [def MyInstance] [keyword extends] [type Sequelize].[type Instance] [operator <] [type MyAttributes] [operator >] {",
+ " [property rawAttributes]: [type MyAttributes];",
+ " [property truthy]: [string 'true'] [operator |] [number 1] [operator |] [atom true];",
+ " [property falsy]: [string 'false'] [operator |] [number 0] [operator |] [atom false];",
+ "}")
+
+ F("flow_extend_operators",
+ "[keyword export] [keyword interface] [def UserModel] [keyword extends]",
+ " [type Sequelize].[type Model] [operator <] [type UserInstance], [type UserAttributes] [operator >] {",
+ " [property findById]: (",
+ " [variable userId]: [type number]",
+ " ) [operator =>] [type Promise] [operator <] [type Array] [operator <] { [property id], [property name] } [operator >>];",
+ " [property updateById]: (",
+ " [variable userId]: [type number],",
+ " [variable isActive]: [type boolean]",
+ " ) [operator =>] [type Promise] [operator <] [type AccountHolderNotificationPreferenceInstance] [operator >];",
+ " }")
+
+ F("flow_interface_with_const",
+ "[keyword const] [def hello]: {",
+ " [property prop1][operator ?]: [type string];",
+ " [property prop2][operator ?]: [type string];",
+ "} [operator =] {};")
+
+ F("flow_double_extend",
+ "[keyword export] [keyword interface] [def UserAttributes] {",
+ " [property id][operator ?]: [type number];",
+ " [property createdAt][operator ?]: [type Date];",
+ "}",
+ "[keyword export] [keyword interface] [def UserInstance] [keyword extends] [type Sequelize].[type Instance][operator <][type UserAttributes][operator >], [type UserAttributes] {",
+ " [property id]: [type number];",
+ " [property createdAt]: [type Date];",
+ "}");
+
+ F("flow_index_signature",
+ "[keyword interface] [def A] {",
+ " [[ [variable prop]: [type string] ]]: [type any];",
+ " [property prop1]: [type any];",
+ "}");
+
+ F("flow_generic_class",
+ "[keyword class] [def Foo][operator <][type T][operator >] {",
+ " [property bar]() {}",
+ " [property foo](): [type Foo] {}",
+ "}")
+
+ F("flow_type_when_keyword",
+ "[keyword export] [keyword type] [type AB] [operator =] [type A] [operator |] [type B];",
+ "[keyword type] [type Flags] [operator =] {",
+ " [property p1]: [type string];",
+ " [property p2]: [type boolean];",
+ "};")
+
+ F("flow_type_when_not_keyword",
+ "[keyword class] [def HasType] {",
+ " [property type]: [type string];",
+ " [property constructor]([def type]: [type string]) {",
+ " [keyword this].[property type] [operator =] [variable-2 type];",
+ " }",
+ " [property setType]({ [def type] }: { [property type]: [type string]; }) {",
+ " [keyword this].[property type] [operator =] [variable-2 type];",
+ " }",
+ "}")
+
+ F("flow_function_generics",
+ "[keyword function] [def a]() {}",
+ "[keyword function] [def b][operator <][type IA] [operator :] { [property type]: [type string]; }, [type IB] [operator :] { [property type]: [type string]; }[operator >]() {}",
+ "[keyword function] [def c]() {}")
+
+ F("flow_complex_return_type",
+ "[keyword function] [def A]() {",
+ " [keyword return] [keyword this].[property property];",
+ "}",
+ "[keyword function] [def B](): [type Promise][operator <]{ [[ [variable key]: [type string] ]]: [type any] } [operator |] [atom null][operator >] {",
+ " [keyword return] [keyword this].[property property];",
+ "}")
+
+ F("flow_complex_type_casting",
+ "[keyword const] [def giftpay] [operator =] ([variable config].[property get]([string 'giftpay']) : { [[ [variable platformUuid]: [type string] ]]: { [property version]: [type number]; [property apiCode]: [type string]; } });")
+
+ F("flow_typeof",
+ "[keyword function] [def x][operator <][type T] [operator :] [keyword typeof] [variable X][operator >]([def a]: [type T]) {",
+ " [keyword return]")
+
+ F("flow_new_typeargs",
+ "[keyword let] [def x] [operator =] [keyword new] [variable Map][operator <][type string], [type Date][operator >]([string-2 `foo${][variable bar][string-2 }`])")
+
+ F("flow modifiers",
+ "[keyword class] [def Foo] {",
+ " [operator +] [property bar] [operator =] () [operator =>] {}",
+ " [operator -] [property baz] [operator =] () [operator =>] {}",
+ " [property constructor]([def x]) {}",
+ "}")
+
+ F("flow arrow prop",
+ "({[property a]: [def p] [operator =>] [variable-2 p]})")
+
+ F("flow generic in function call",
+ "[keyword this].[property a][operator <][type Type][operator >]([variable foo]);",
+ "[keyword this].[property a][operator <][variable Type][operator >][variable foo];")
+
+ F("flow type guard",
+ "[keyword class] [def Appler] {",
+ " [keyword static] [property assertApple]([def fruit]: [type Fruit]): [type boolean] [operator %checks] {",
+ " [keyword if] ([operator !]([variable-2 fruit] [keyword instanceof] [variable Apple]))",
+ " [keyword throw] [keyword new] [variable Error]();",
+ " }",
+ "}")
+
+ F("flow type as variable",
+ "[variable type] [operator =] ( [variable x] : [type Bar] );");
+
+ F("flow enum body",
+ "[keyword export] [keyword const] [keyword enum] [def CodeInspectionResultType] {",
+ " [def ERROR] [operator =] [string 'problem_type_error'],",
+ " [def WARNING] [operator =] [string 'problem_type_warning'],",
+ " [def META],",
+ "}")
+
+ F("flow parenthesized type",
+ "[keyword class] [def Foo] {",
+ " [property x] [operator =] [keyword new] [variable A][operator <][type B], [type string][operator |](() [operator =>] [type void])[operator >]();",
+ " [property bar]();",
+ "}")
+
+ F("flow interface without semicolons",
+ "[keyword interface] [def Foo] {",
+ " [property greet]([def x]: [type int]): [type blah]",
+ " [property bar]: [type void]",
+ "}")
+
var jsonld_mode = CodeMirror.getMode(
{indentUnit: 2},
{name: "javascript", jsonld: true}
diff --git a/mode/jsx/index.html b/mode/jsx/index.html
index 4df632e1f7..64d1ff9dfa 100644
--- a/mode/jsx/index.html
+++ b/mode/jsx/index.html
@@ -84,6 +84,6 @@ JSX mode
JSX Mode for React's
JavaScript syntax extension.
-MIME types defined: text/jsx
, text/typescript-jsx
.
+MIME types defined: text/jsx
, text/typescript-jsx
, text/flow-jsx
.
diff --git a/mode/jsx/jsx.js b/mode/jsx/jsx.js
index 889d3fe5e7..036ab30257 100644
--- a/mode/jsx/jsx.js
+++ b/mode/jsx/jsx.js
@@ -145,4 +145,5 @@
CodeMirror.defineMIME("text/jsx", "jsx")
CodeMirror.defineMIME("text/typescript-jsx", {name: "jsx", base: {name: "javascript", typescript: true}})
+ CodeMirror.defineMIME("text/flow-jsx", {name: "jsx", base: {name: "javascript", flow: true}})
});