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}}) });