From 3ba23d3758e06d95b14ed4e89607e258d15ddbf7 Mon Sep 17 00:00:00 2001 From: Simeon Simeonov Date: Wed, 12 Oct 2022 23:21:07 +0200 Subject: Add Rust mode and update lua-mode, markdown-mode and yaml-mode --- .emacs.d/lisp/rust-mode-tests.el | 3637 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 3637 insertions(+) create mode 100644 .emacs.d/lisp/rust-mode-tests.el (limited to '.emacs.d/lisp/rust-mode-tests.el') diff --git a/.emacs.d/lisp/rust-mode-tests.el b/.emacs.d/lisp/rust-mode-tests.el new file mode 100644 index 0000000..e4949b2 --- /dev/null +++ b/.emacs.d/lisp/rust-mode-tests.el @@ -0,0 +1,3637 @@ +;;; rust-mode-tests.el --- ERT tests for rust-mode.el -*- lexical-binding: t; -*- + +(require 'rust-mode) +(require 'ert) +(require 'cl-lib) +(require 'compile) +(require 'imenu) + +(defconst rust-test-fill-column 32) +(setq-default indent-tabs-mode nil) + +(defmacro rust-test-silence (messages &rest body) + `(let ((f (lambda (orig-fun format-string &rest args) + (unless (member format-string ,messages) + (apply orig-fun format-string args))))) + (unwind-protect + (progn + (advice-add 'message :around f) + ,@body) + (advice-remove 'message f)))) + +(defun rust-compare-code-after-manip (_original _point-pos _manip-func expected got) + (equal expected got)) + +(defun rust-test-explain-bad-manip (original point-pos _manip-func expected got) + (if (equal expected got) + nil + (list + ;; The (goto-char) and (insert) business here is just for + ;; convenience--after an error, you can copy-paste that into emacs eval to + ;; insert the bare strings into a buffer + "Rust code was manipulated wrong after:" + `(insert ,original) + `(goto-char ,point-pos) + 'expected `(insert ,expected) + 'got `(insert ,got) + (cl-loop for i from 0 to (max (length original) (length expected)) + for oi = (if (< i (length got)) (elt got i)) + for ei = (if (< i (length expected)) (elt expected i)) + while (equal oi ei) + finally return `(first-difference-at + (goto-char ,(+ 1 i)) + expected ,(char-to-string ei) + got ,(char-to-string oi)))))) +(put 'rust-compare-code-after-manip 'ert-explainer + 'rust-test-explain-bad-manip) + +(defun rust-test-manip-code (original point-pos manip-func expected) + (with-temp-buffer + (rust-mode) + (insert original) + (goto-char point-pos) + (funcall manip-func) + (should (rust-compare-code-after-manip + original point-pos manip-func expected (buffer-string))))) + +(defun rust-test-fill-paragraph (unfilled expected &optional start-pos end-pos) + "We're going to run through many scenarios here--the point should be able to be anywhere from the start-pos (defaults to 1) through end-pos (defaults to the length of what was passed in) and (fill-paragraph) should return the same result. It should also work with fill-region from start-pos to end-pos. + +Also, the result should be the same regardless of whether the code is at the beginning or end of the file. (If you're not careful, that can make a difference.) So we test each position given above with the passed code at the beginning, the end, neither and both. So we do this a total of 1 + (end-pos - start-pos)*4 times. Oy." + (let* ((start-pos (or start-pos 1)) + (end-pos (or end-pos (length unfilled))) + (padding "\n \n") + (padding-len (length padding))) + (cl-loop + for pad-at-beginning from 0 to 1 + do (cl-loop for pad-at-end from 0 to 1 + with padding-beginning = (if (= 0 pad-at-beginning) "" padding) + with padding-end = (if (= 0 pad-at-end) "" padding) + with padding-adjust = (* padding-len pad-at-beginning) + ;; If we're adding space to the beginning, and our start position + ;; is at the very beginning, we want to test within the added space. + ;; Otherwise adjust the start and end for the beginning padding. + with start-pos = (if (= 1 start-pos) 1 (+ padding-adjust start-pos)) + with end-pos = (+ end-pos padding-adjust) + do (cl-loop for pos from start-pos to end-pos + do (rust-test-manip-code + (concat padding-beginning unfilled padding-end) + pos + (lambda () + (let ((fill-column rust-test-fill-column)) + (fill-paragraph))) + (concat padding-beginning expected padding-end))))) + ;; In addition to all the fill-paragraph tests, check that it works using fill-region + (rust-test-manip-code + unfilled + start-pos + (lambda () + (let ((fill-column rust-test-fill-column)) + (fill-region start-pos end-pos))) + expected) + )) + +(ert-deftest fill-paragraph-top-level-multi-line-style-doc-comment-second-line () + (rust-test-fill-paragraph + "/** + * This is a very very very very very very very long string + */" + "/** + * This is a very very very very + * very very very long string + */")) + + +(ert-deftest fill-paragraph-top-level-multi-line-style-doc-comment-first-line () + (rust-test-fill-paragraph + "/** This is a very very very very very very very long string + */" + "/** This is a very very very + * very very very very long + * string + */")) + +(ert-deftest fill-paragraph-multi-paragraph-multi-line-style-doc-comment () + (let + ((multi-paragraph-unfilled + "/** + * This is the first really really really really really really really long paragraph + * + * This is the second really really really really really really long paragraph + */")) + (rust-test-fill-paragraph + multi-paragraph-unfilled + "/** + * This is the first really + * really really really really + * really really long paragraph + * + * This is the second really really really really really really long paragraph + */" + 1 89) + (rust-test-fill-paragraph + multi-paragraph-unfilled + "/** + * This is the first really really really really really really really long paragraph + * + * This is the second really + * really really really really + * really long paragraph + */" + 90))) + +(ert-deftest fill-paragraph-multi-paragraph-single-line-style-doc-comment () + (let + ((multi-paragraph-unfilled + "/// This is the first really really really really really really really long paragraph +/// +/// This is the second really really really really really really long paragraph")) + (rust-test-fill-paragraph + multi-paragraph-unfilled + "/// This is the first really +/// really really really really +/// really really long paragraph +/// +/// This is the second really really really really really really long paragraph" + 1 86) + (rust-test-fill-paragraph + multi-paragraph-unfilled + "/// This is the first really really really really really really really long paragraph +/// +/// This is the second really +/// really really really really +/// really long paragraph" + 87))) + +(ert-deftest fill-paragraph-multi-paragraph-single-line-style-indented () + (rust-test-fill-paragraph + " // This is the first really really really really really really really long paragraph + // + // This is the second really really really really really really long paragraph" + " // This is the first really + // really really really + // really really really + // long paragraph + // + // This is the second really really really really really really long paragraph" 1 89)) + +(ert-deftest fill-paragraph-multi-line-style-comment () + (rust-test-fill-paragraph + "/* This is a very very very very very very very very long string + */" + "/* This is a very very very very + * very very very very long + * string + */")) + +(ert-deftest fill-paragraph-multi-line-style-inner-doc-comment () + (rust-test-fill-paragraph + "/*! This is a very very very very very very very long string + */" + "/*! This is a very very very + * very very very very long + * string + */")) + +(ert-deftest fill-paragraph-single-line-style-inner-doc-comment () + (rust-test-fill-paragraph + "//! This is a very very very very very very very long string" + "//! This is a very very very +//! very very very very long +//! string")) + +(ert-deftest fill-paragraph-prefixless-multi-line-doc-comment () + (rust-test-fill-paragraph + "/** +This is my summary. Blah blah blah blah blah. Dilly dally dilly dally dilly dally doo. + +This is some more text. Fee fie fo fum. Humpty dumpty sat on a wall. +*/" + "/** +This is my summary. Blah blah +blah blah blah. Dilly dally +dilly dally dilly dally doo. + +This is some more text. Fee fie fo fum. Humpty dumpty sat on a wall. +*/" 4 90)) + +(ert-deftest fill-paragraph-with-no-space-after-star-prefix () + (rust-test-fill-paragraph + "/** + *This is a very very very very very very very long string + */" + "/** + *This is a very very very very + *very very very long string + */")) + +(ert-deftest fill-paragraph-single-line-style-with-code-before () + (rust-test-fill-paragraph + "fn foo() { } +/// This is my comment. This is more of my comment. This is even more." + "fn foo() { } +/// This is my comment. This is +/// more of my comment. This is +/// even more." 14)) + +(ert-deftest fill-paragraph-single-line-style-with-code-after () + (rust-test-fill-paragraph + "/// This is my comment. This is more of my comment. This is even more. +fn foo() { }" + "/// This is my comment. This is +/// more of my comment. This is +/// even more. +fn foo() { }" 1 73)) + +(ert-deftest fill-paragraph-single-line-style-code-before-and-after () + (rust-test-fill-paragraph + "fn foo() { } +/// This is my comment. This is more of my comment. This is even more. +fn bar() { }" + "fn foo() { } +/// This is my comment. This is +/// more of my comment. This is +/// even more. +fn bar() { }" 14 85)) + +(defun test-auto-fill (initial position inserted expected) + (rust-test-manip-code + initial + position + (lambda () + (unwind-protect + (progn + (let ((fill-column rust-test-fill-column)) + (auto-fill-mode) + (goto-char position) + (insert inserted) + (syntax-ppss-flush-cache 1) + (funcall auto-fill-function))) + (auto-fill-mode t))) + expected)) + +(ert-deftest auto-fill-multi-line-doc-comment () + (test-auto-fill + "/** + * + */" + 7 + " This is a very very very very very very very long string" + "/** + * This is a very very very very + * very very very long string + */")) + +(ert-deftest auto-fill-single-line-doc-comment () + (test-auto-fill + "/// This is the first really +/// really really really really +/// really really long paragraph +/// +/// " + 103 + "This is the second really really really really really really long paragraph" + "/// This is the first really +/// really really really really +/// really really long paragraph +/// +/// This is the second really +/// really really really really +/// really long paragraph" + )) + +(ert-deftest auto-fill-multi-line-prefixless () + (test-auto-fill + "/* + + */" + 4 + "This is a very very very very very very very long string" + "/* +This is a very very very very +very very very long string + */" + )) + +(defun test-indent (indented &optional deindented) + (let ((deindented (or deindented (replace-regexp-in-string "^[[:blank:]]*" " " indented)))) + (rust-test-manip-code + deindented + 1 + (lambda () + (rust-test-silence + '("%s %s" ; "Indenting..." progress-reporter-do-update + "%sdone") ; "Indenting...done" progress-reporter-done + (indent-region 1 (+ 1 (buffer-size))))) + indented))) + + +(ert-deftest indent-struct-fields-aligned () + (test-indent + " +struct Foo { bar: i32, + baz: i32 } + +struct Blah {x:i32, + y:i32, + z:String")) + +(ert-deftest indent-doc-comments () + (test-indent + " +/** + * This is a doc comment + * + */ + +/// So is this + +fn foo() { + /*! + * this is a nested doc comment + */ + \n //! And so is this +}")) + +(ert-deftest indent-inside-braces () + (test-indent + " +// struct fields out one level: +struct foo { + a:i32, + // comments too + b:char +} + +fn bar(x:Box) { // comment here should not affect the next indent + bla(); + bla(); +}")) + +(ert-deftest indent-top-level () + (test-indent + " +// Everything here is at the top level and should not be indented +#[attrib] +mod foo; + +pub static bar = Quux{a: b()} + +use foo::bar::baz; + +fn foo() { } +")) + +(ert-deftest font-lock-multi-raw-strings-in-a-row () + (rust-test-font-lock + " +r\"foo\\\", \"bar\", r\"bar\"; +r\"foo\\.\", \"bar\", r\"bar\"; +r\"foo\\..\", \"bar\", r\"foo\\..\\bar\"; +r\"\\\", \"foo\", r\"\\foo\"; +not_a_string(); + +" + + (apply #'append (mapcar (lambda (s) (list s 'font-lock-string-face)) + '("r\"foo\\\"" "\"bar\"" "r\"bar\"" + "r\"foo\\.\"" "\"bar\"" "r\"bar\"" + "r\"foo\\..\"" "\"bar\"" "r\"foo\\..\\bar\"" + "r\"\\\"" "\"foo\"" "r\"\\foo\""))) + )) + +(ert-deftest font-lock-raw-string-after-normal-string-ending-in-r () + (rust-test-font-lock + "\"bar\" r\"foo\"" + '("\"bar\"" font-lock-string-face "r\"foo\"" font-lock-string-face))) + +(ert-deftest indent-params-no-align () + (test-indent + " +// Indent out one level because no params appear on the first line +fn xyzzy( + a:i32, + b:char) { } + +fn abcdef( + a:i32, + b:char) + -> char +{ }")) + +(ert-deftest indent-params-align () + (test-indent + " +// Align the second line of params to the first +fn foo(a:i32, + b:char) { } + +fn bar( a:i32, + b:char) + -> i32 +{ } + +fn baz( a:i32, // should work with a comment here + b:char) + -> i32 +{ } +")) + +(ert-deftest indent-open-after-arrow () + (test-indent + " +// Indent function body only one level after `-> {` +fn foo1(a:i32, b:char) -> i32 { + let body; +} + +fn foo2(a:i32, + b:char) -> i32 { + let body; +} + +fn foo3(a:i32, + b:char) + -> i32 { + let body; +} + +fn foo4(a:i32, + b:char) + -> i32 where i32:A { + let body; +} +")) + +(ert-deftest indent-return-type-non-visual () + (let ((rust-indent-return-type-to-arguments nil)) +(test-indent + " +fn imagine_long_enough_to_wrap_at_arrow(a:i32, b:char) + -> i32 +{ + let body; +} +"))) + +(ert-deftest indent-body-after-where () + (let ((rust-indent-where-clause t)) + (test-indent + " +fn foo1(a: A, b: B) -> A + where A: Clone + Default, B: Eq { + let body; + Foo { + bar: 3 + } +} + +fn foo2(a: A, b: B) -> A + where A: Clone + Default, B: Eq +{ + let body; + Foo { + bar: 3 + } +} +"))) + +(ert-deftest indent-align-where-clauses-style1a () + (let ((rust-indent-where-clause t)) + (test-indent + " +fn foo1a(a: A, b: B, c: C) -> D + where A: Clone + Default, + B: Eq, + C: PartialEq, + D: PartialEq { + let body; + Foo { + bar: 3 + } +} +"))) + +(ert-deftest indent-align-where-clauses-style1b () + (let ((rust-indent-where-clause t)) + (test-indent + " +fn foo1b(a: A, b: B, c: C) -> D + where A: Clone + Default, + B: Eq, + C: PartialEq, + D: PartialEq +{ + let body; + Foo { + bar: 3 + } +} +"))) + +(ert-deftest indent-align-where-clauses-style2a () + (test-indent + " +fn foo2a(a: A, b: B, c: C) -> D where A: Clone + Default, + B: Eq, + C: PartialEq, + D: PartialEq { + let body; + Foo { + bar: 3 + } +} +")) + +(ert-deftest indent-align-where-clauses-style2b () + (test-indent + " +fn foo2b(a: A, b: B, c: C) -> D where A: Clone + Default, + B: Eq, + C: PartialEq, + D: PartialEq +{ + let body; + Foo { + bar: 3 + } +} +")) + +(ert-deftest indent-align-where-clauses-style3a () + (test-indent + " +fn foo3a(a: A, b: B, c: C) -> D where + A: Clone + Default, + B: Eq, + C: PartialEq, + D: PartialEq { + let body; + Foo { + bar: 3 + } +} +")) + +(ert-deftest indent-align-where-clauses-style3b () + (test-indent + " +fn foo3b(a: A, b: B, c: C) -> D where + A: Clone + Default, + B: Eq, + C: PartialEq, + D: PartialEq +{ + let body; + Foo { + bar: 3 + } +} +")) + +(ert-deftest indent-align-where-clauses-style4a () + (let ((rust-indent-where-clause nil)) + (test-indent + " +fn foo4a(a: A, b: B, c: C) -> D +where A: Clone + Default, + B: Eq, + C: PartialEq, + D: PartialEq { + let body; + Foo { + bar: 3 + } +} +"))) + +(ert-deftest indent-align-where-clauses-style4b () + (let ((rust-indent-where-clause nil)) + (test-indent + " +fn foo4b(a: A, b: B, c: C) -> D +where A: Clone + Default, + B: Eq, + C: PartialEq, + D: PartialEq +{ + let body; + Foo { + bar: 3 + } +} +"))) + +(ert-deftest indent-align-where-clauses-impl-example () + (let ((rust-indent-where-clause t)) + (test-indent + " +impl<'a, K, Q: ?Sized, V, S> Index<&'a Q> for HashMap + where K: Eq + Hash + Borrow, + Q: Eq + Hash, + S: HashState, +{ + let body; + Foo { + bar: 3 + } +} +"))) + +(ert-deftest indent-align-where-clauses-first-line () + (let ((rust-indent-where-clause t)) + (test-indent + "fn foo1(a: A, b: B) -> A + where A: Clone + Default, B: Eq { + let body; + Foo { + bar: 3 + } +} +"))) + +(ert-deftest indent-align-where-in-comment1 () + (test-indent + "/// - there must not exist an edge U->V in the graph where: +#[derive(Clone, PartialEq, Eq)] +pub struct Region { // <-- this should be flush with left margin! + entry: BasicBlockIndex, + leaves: BTreeMap, +} +")) + +(ert-deftest indent-align-where-in-comment2 () + (let ((rust-indent-where-clause t)) + (test-indent + "fn foo(f:F, g:G) + where F:Send, +// where + G:Sized +{ + let body; +} +"))) + +(ert-deftest indent-align-where-in-comment3 () + (let ((rust-indent-where-clause t)) + (test-indent + "fn foo(f:F, g:G) + where F:Send, +// where F:ThisIsNotActualCode, + G:Sized +{ + let body; +} +"))) + +(ert-deftest indent-square-bracket-alignment () + (test-indent + " +fn args_on_the_next_line( // with a comment + a:i32, + b:String) { + let aaaaaa = [ + 1, + 2, + 3]; + let bbbbbbb = [1, 2, 3, + 4, 5, 6]; + let ccc = [ 10, 9, 8, + 7, 6, 5]; +} +")) + +(ert-deftest indent-closing-square-bracket () + (test-indent + "fn blergh() { + let list = vec![ + 1, + 2, + 3, + ]; +}")) + +(ert-deftest indent-closing-paren () + (test-indent + "fn blergh() { + call( + a, + function + ); +}")) + +(ert-deftest indent-nested-fns () + (test-indent + " +fn nexted_fns(a: fn(b:i32, + c:char) + -> i32, + d: i32) + -> u128 +{ + 0 +} +" + )) + +(ert-deftest indent-multi-line-expr () + (test-indent + " +fn foo() +{ + x(); + let a = + b(); +} +" + )) + +(ert-deftest indent-match () + (test-indent + " +fn foo() { + match blah { + Pattern => stuff(), + _ => whatever + } +} +" + )) + +(ert-deftest indent-match-multiline-pattern () + (test-indent + " +fn foo() { + match blah { + Pattern | + Pattern2 => { + hello() + }, + _ => whatever + } +} +" + )) + +(ert-deftest indent-indented-match () + (test-indent + " +fn foo() { + let x = + match blah { + Pattern | + Pattern2 => { + hello() + }, + _ => whatever + }; + y(); +} +" + )) + +(ert-deftest indent-curly-braces-within-parens () + (test-indent + " +fn foo() { + let x = + foo(bar(|x| { + only_one_indent_here(); + })); + y(); +} +" + )) + +(ert-deftest indent-weirdly-indented-block () + (rust-test-manip-code + " +fn foo() { + { +this_block_is_over_to_the_left_for_some_reason(); + } + +} +" + 16 + #'indent-for-tab-command + " +fn foo() { + { + this_block_is_over_to_the_left_for_some_reason(); + } + +} +" + )) + +(ert-deftest indent-multi-line-attrib () + (test-indent + " +#[attrib( + this, + that, + theotherthing)] +fn function_with_multiline_attribute() {} +" + )) + + +;; Make sure that in effort to cover match patterns we don't mistreat || or expressions +(ert-deftest indent-nonmatch-or-expression () + (test-indent + " +fn foo() { + let x = foo() || + bar(); +} +" + )) + +;; Closing braces in single char literals and strings should not confuse the indentation +(ert-deftest indent-closing-braces-in-char-literals () + (test-indent + " +fn foo() { + { bar('}'); } + { bar(']'); } + { bar(')'); } +} +" + )) + +;; This is a test for #103: a comment after the last struct member that does +;; not have a trailing comma. The comment used to be indented one stop too +;; far. +(ert-deftest indent-comment-after-last-struct-member () + (test-indent + " +struct A { + x: u8 + // comment +} + +struct A { + x: u8 + /* comment */ +} +" + )) + +(defconst rust-test-motion-string + " +fn fn1(arg: i32) -> bool { + let x = 5; + let y = b(); + true +} + +fn fn2(arg: i32) -> bool { + let x = 5; + let y = b(); + true +} + +pub fn fn3(arg: i32) -> bool { + let x = 5; + let y = b(); + true +} + +struct Foo { + x: i32 +} +") +(defconst rust-test-region-string rust-test-motion-string) +(defconst rust-test-indent-motion-string + " +fn blank_line(arg:i32) -> bool { + +} + +fn indenting_closing_brace() { + if(true) { +} +} + +fn indenting_middle_of_line() { + if(true) { + push_me_out(); +} else { + pull_me_back_in(); +} +} + +fn indented_already() { + + // The previous line already has its spaces +} +") + +;; Symbol -> (line column) +(defconst rust-test-positions-alist '((start-of-fn1 (2 0)) + (start-of-fn1-middle-of-line (2 15)) + (middle-of-fn1 (3 7)) + (end-of-fn1 (6 0)) + (between-fn1-fn2 (7 0)) + (start-of-fn2 (8 0)) + (middle-of-fn2 (10 4)) + (before-start-of-fn1 (1 0)) + (after-end-of-fn2 (13 0)) + (beginning-of-fn3 (14 0)) + (middle-of-fn3 (16 4)) + (middle-of-struct (21 10)) + (before-start-of-struct (19 0)) + (after-end-of-struct (23 0)) + (blank-line-indent-start (3 0)) + (blank-line-indent-target (3 4)) + (closing-brace-indent-start (8 1)) + (closing-brace-indent-target (8 5)) + (middle-push-indent-start (13 2)) + (middle-push-indent-target (13 9)) + (after-whitespace-indent-start (13 1)) + (after-whitespace-indent-target (13 8)) + (middle-pull-indent-start (15 19)) + (middle-pull-indent-target (15 12)) + (blank-line-indented-already-bol-start (20 0)) + (blank-line-indented-already-bol-target (20 4)) + (blank-line-indented-already-middle-start (20 2)) + (blank-line-indented-already-middle-target (20 4)) + (nonblank-line-indented-already-bol-start (21 0)) + (nonblank-line-indented-already-bol-target (21 4)) + (nonblank-line-indented-already-middle-start (21 2)) + (nonblank-line-indented-already-middle-target (21 4)))) + +(defun rust-get-buffer-pos (pos-symbol) + "Get buffer position from POS-SYMBOL. + +POS-SYMBOL is a symbol found in `rust-test-positions-alist'. +Convert the line-column information from that list into a buffer position value." + (interactive "P") + (let* ( + (line-and-column (cadr (assoc pos-symbol rust-test-positions-alist))) + (line (nth 0 line-and-column)) + (column (nth 1 line-and-column))) + (save-excursion + (goto-char (point-min)) + (forward-line (1- line)) + (move-to-column column) + (point)))) + +;;; FIXME: Maybe add an ERT explainer function (something that shows the +;;; surrounding code of the final point, not just the position). +(defun rust-test-motion (source-code init-pos final-pos manip-func &rest args) + "Test that MANIP-FUNC moves point from INIT-POS to FINAL-POS. + +If ARGS are provided, send them to MANIP-FUNC. + +INIT-POS, FINAL-POS are position symbols found in `rust-test-positions-alist'." + (with-temp-buffer + (rust-mode) + (insert source-code) + (goto-char (rust-get-buffer-pos init-pos)) + (apply manip-func args) + (should (equal (point) (rust-get-buffer-pos final-pos))))) + +(defun rust-test-region (source-code init-pos reg-beg reg-end manip-func &rest args) + "Test that MANIP-FUNC marks region from REG-BEG to REG-END. + +INIT-POS is the initial position of point. +If ARGS are provided, send them to MANIP-FUNC. +All positions are position symbols found in `rust-test-positions-alist'." + (with-temp-buffer + (rust-mode) + (insert source-code) + (goto-char (rust-get-buffer-pos init-pos)) + (apply manip-func args) + (should (equal (list (region-beginning) (region-end)) + (list (rust-get-buffer-pos reg-beg) + (rust-get-buffer-pos reg-end)))))) + +(ert-deftest rust-beginning-of-defun-from-middle-of-fn () + (rust-test-motion + rust-test-motion-string + 'middle-of-fn1 + 'start-of-fn1 + #'beginning-of-defun)) + +(ert-deftest rust-beginning-of-defun-from-end () + (rust-test-motion + rust-test-motion-string + 'end-of-fn1 + 'start-of-fn1 + #'beginning-of-defun)) + +(ert-deftest rust-beginning-of-defun-before-open-brace () + (rust-test-motion + rust-test-motion-string + 'start-of-fn1-middle-of-line + 'start-of-fn1 + #'beginning-of-defun)) + +(ert-deftest rust-beginning-of-defun-between-fns () + (rust-test-motion + rust-test-motion-string + 'between-fn1-fn2 + 'start-of-fn1 + #'beginning-of-defun)) + +(ert-deftest rust-beginning-of-defun-with-arg () + (rust-test-motion + rust-test-motion-string + 'middle-of-fn2 + 'start-of-fn1 + #'beginning-of-defun 2)) + +(ert-deftest rust-beginning-of-defun-with-negative-arg () + (rust-test-motion + rust-test-motion-string + 'middle-of-fn1 + 'beginning-of-fn3 + #'beginning-of-defun -2)) + +(ert-deftest rust-beginning-of-defun-pub-fn () + (rust-test-motion + rust-test-motion-string + 'middle-of-fn3 + 'beginning-of-fn3 + #'beginning-of-defun)) + +(ert-deftest rust-beginning-of-defun-string-comment () + (let (fn-1 fn-2 p-1 p-2) + (with-temp-buffer + (rust-mode) + (insert "fn test1() { + let s=r#\" +fn test2(); +\"#;") + (setq p-1 (point)) + (setq fn-1 (1+ p-1)) + (insert " +fn test3() { + /* +fn test4();") + (setq p-2 (point)) + (insert "\n*/\n}\n") + (setq fn-2 (point)) + (insert "fn test5() { }") + + (goto-char p-1) + (beginning-of-defun) + (should (eq (point) (point-min))) + + (beginning-of-defun -2) + (should (eq (point) fn-2)) + + (goto-char p-2) + (beginning-of-defun) + (should (eq (point) fn-1)) + + (beginning-of-defun -1) + (should (eq (point) fn-2)) + + (goto-char (point-max)) + (beginning-of-defun 2) + (should (eq (point) fn-1))))) + +(ert-deftest rust-end-of-defun-from-middle-of-fn () + (rust-test-motion + rust-test-motion-string + 'middle-of-fn1 + 'between-fn1-fn2 + #'end-of-defun)) + +(ert-deftest rust-end-of-defun-from-beg () + (rust-test-motion + rust-test-motion-string + 'start-of-fn1 + 'between-fn1-fn2 + #'end-of-defun)) + +(ert-deftest rust-end-of-defun-before-open-brace () + (rust-test-motion + rust-test-motion-string + 'start-of-fn1-middle-of-line + 'between-fn1-fn2 + #'end-of-defun)) + +(ert-deftest rust-end-of-defun-between-fns () + (rust-test-motion + rust-test-motion-string + 'between-fn1-fn2 + 'after-end-of-fn2 + #'end-of-defun)) + +(ert-deftest rust-end-of-defun-with-arg () + (rust-test-motion + rust-test-motion-string + 'middle-of-fn1 + 'after-end-of-fn2 + #'end-of-defun 2)) + +(ert-deftest rust-end-of-defun-with-negative-arg () + (rust-test-motion + rust-test-motion-string + 'middle-of-fn3 + 'between-fn1-fn2 + #'end-of-defun -2)) + +(ert-deftest rust-mark-defun-from-middle-of-fn () + (rust-test-region + rust-test-region-string + 'middle-of-fn2 + 'between-fn1-fn2 'after-end-of-fn2 + #'mark-defun)) + +(ert-deftest rust-mark-defun-from-end () + (rust-test-region + rust-test-region-string + 'end-of-fn1 + 'before-start-of-fn1 'between-fn1-fn2 + #'mark-defun)) + +(ert-deftest rust-mark-defun-start-of-defun () + (rust-test-region + rust-test-region-string + 'start-of-fn2 + 'between-fn1-fn2 'after-end-of-fn2 + #'mark-defun)) + +(ert-deftest rust-mark-defun-from-middle-of-struct () + (rust-test-region + rust-test-region-string + 'middle-of-struct + 'before-start-of-struct 'after-end-of-struct + #'mark-defun)) + +(ert-deftest indent-line-blank-line-motion () + (rust-test-motion + rust-test-indent-motion-string + 'blank-line-indent-start + 'blank-line-indent-target + #'indent-for-tab-command)) + +(ert-deftest indent-line-closing-brace-motion () + (rust-test-motion + rust-test-indent-motion-string + 'closing-brace-indent-start + 'closing-brace-indent-target + #'indent-for-tab-command)) + +(ert-deftest indent-line-middle-push-motion () + (rust-test-motion + rust-test-indent-motion-string + 'middle-push-indent-start + 'middle-push-indent-target + #'indent-for-tab-command)) + +(ert-deftest indent-line-after-whitespace-motion () + (rust-test-motion + rust-test-indent-motion-string + 'after-whitespace-indent-start + 'after-whitespace-indent-target + #'indent-for-tab-command)) + +(ert-deftest indent-line-middle-pull-motion () + (rust-test-motion + rust-test-indent-motion-string + 'middle-pull-indent-start + 'middle-pull-indent-target + #'indent-for-tab-command)) + +(ert-deftest indent-line-blank-line-indented-already-bol () + (rust-test-motion + rust-test-indent-motion-string + 'blank-line-indented-already-bol-start + 'blank-line-indented-already-bol-target + #'indent-for-tab-command)) + +(ert-deftest indent-line-blank-line-indented-already-middle () + (rust-test-motion + rust-test-indent-motion-string + 'blank-line-indented-already-middle-start + 'blank-line-indented-already-middle-target + #'indent-for-tab-command)) + +(ert-deftest indent-line-nonblank-line-indented-already-bol () + (rust-test-motion + rust-test-indent-motion-string + 'nonblank-line-indented-already-bol-start + 'nonblank-line-indented-already-bol-target + #'indent-for-tab-command)) + +(ert-deftest indent-line-nonblank-line-indented-already-middle () + (rust-test-motion + rust-test-indent-motion-string + 'nonblank-line-indented-already-middle-start + 'nonblank-line-indented-already-middle-target + #'indent-for-tab-command)) + +(ert-deftest no-stack-overflow-in-rust-rewind-irrelevant () + (with-temp-buffer + (rust-mode) + (insert "fn main() {\n let x = 1;") + ;; Insert 150 separate comments on the same line + (dotimes (_i 150) + (insert "/* foo */ ")) + ;; Rewinding from the last comment to the end of the let needs at least + ;; 150 iterations, but if we limit the stack depth to 100 (this appears to + ;; be some minimum), a recursive function would overflow, throwing an + ;; error. + (let ((max-lisp-eval-depth 100)) + (rust-rewind-irrelevant) + ;; Only a non-stack overflowing function would make it this far. Also + ;; check that we rewound till after the ; + (should (= (char-before) ?\;))))) + +(defun rust-test-fontify-string (str) + (with-temp-buffer + (rust-mode) + (insert str) + (font-lock-flush) + (font-lock-ensure) + (buffer-string))) + +(defun rust-test-group-str-by-face (str) + "Fontify `STR' in rust-mode and group it by face, returning a +list of substrings of `STR' each followed by its face." + (cl-loop with fontified = (rust-test-fontify-string str) + for start = 0 then end + while start + for end = (next-single-property-change start 'face fontified) + for prop = (get-text-property start 'face fontified) + for text = (substring-no-properties fontified start end) + if prop + append (list text prop))) + +(defun rust-test-font-lock (source face-groups) + "Test that `SOURCE' fontifies to the expected `FACE-GROUPS'" + (should (equal (rust-test-group-str-by-face source) + face-groups))) + +(ert-deftest font-lock-attribute-simple () + (rust-test-font-lock + "#[foo]" + '("#[foo]" font-lock-preprocessor-face))) + +(ert-deftest font-lock-attribute-inner () + (rust-test-font-lock + "#![foo]" + '("#![foo]" font-lock-preprocessor-face))) + +(ert-deftest font-lock-attribute-key-value () + (rust-test-font-lock + "#[foo = \"bar\"]" + '("#[foo = " font-lock-preprocessor-face + "\"bar\"" font-lock-string-face + "]" font-lock-preprocessor-face))) + +(ert-deftest font-lock-attribute-around-comment () + (rust-test-font-lock + "#[foo /* bar */]" + '("#[foo " font-lock-preprocessor-face + "/* " font-lock-comment-delimiter-face + "bar */" font-lock-comment-face + "]" font-lock-preprocessor-face))) + +(ert-deftest font-lock-attribute-inside-string () + (rust-test-font-lock + "\"#[foo]\"" + '("\"#[foo]\"" font-lock-string-face))) + +(ert-deftest font-lock-attribute-inside-comment () + (rust-test-font-lock + "/* #[foo] */" + '("/* " font-lock-comment-delimiter-face + "#[foo] */" font-lock-comment-face))) + +(ert-deftest font-lock-number-with-type () + (rust-test-font-lock + "-123i32" + '("i32" font-lock-type-face)) + (rust-test-font-lock + "123u32" + '("u32" font-lock-type-face)) + (rust-test-font-lock + "123_123_u32" + '("u32" font-lock-type-face)) + (rust-test-font-lock + "0xff_u8" + '("u8" font-lock-type-face)) + (rust-test-font-lock + "0b1111_1111_1001_0000i64" + '("i64" font-lock-type-face)) + (rust-test-font-lock + "0usize" + '("usize" font-lock-type-face)) + (rust-test-font-lock + "123.0f64 + 1." + '("f64" font-lock-type-face)) + (rust-test-font-lock + "0.1f32" + '("f32" font-lock-type-face)) + (rust-test-font-lock + "12E+99_f64" + '("f64" font-lock-type-face)) + (rust-test-font-lock + "5f32" + '("f32" font-lock-type-face)) + (rust-test-font-lock + "0x5i32" + '("i32" font-lock-type-face)) + (rust-test-font-lock + "1x5i32" + '()) + (rust-test-font-lock + "0x5i321" + '()) + (rust-test-font-lock + "fname5f32" + '()) + (rust-test-font-lock + "0x5i32+1" + '("i32" font-lock-type-face)) + (rust-test-font-lock + "f(0xFFi32)" + '("i32" font-lock-type-face))) + +(ert-deftest font-lock-double-quote-character-literal () + (rust-test-font-lock + "'\"'; let" + '("'\"'" font-lock-string-face + "let" font-lock-keyword-face))) + +(ert-deftest font-lock-fn-contains-capital () + (rust-test-font-lock + "fn foo_Bar() {}" + '("fn" font-lock-keyword-face + "foo_Bar" font-lock-function-name-face))) + +(ert-deftest font-lock-let-bindings () + (rust-test-font-lock + "let foo;" + '("let" font-lock-keyword-face + "foo" font-lock-variable-name-face)) + (rust-test-font-lock + "let ref foo;" + '("let" font-lock-keyword-face + "ref" font-lock-keyword-face + "foo" font-lock-variable-name-face)) + (rust-test-font-lock + "let mut foo;" + '("let" font-lock-keyword-face + "mut" font-lock-keyword-face + "foo" font-lock-variable-name-face)) + (rust-test-font-lock + "let foo = 1;" + '("let" font-lock-keyword-face + "foo" font-lock-variable-name-face)) + (rust-test-font-lock + "let mut foo = 1;" + '("let" font-lock-keyword-face + "mut" font-lock-keyword-face + "foo" font-lock-variable-name-face)) + (rust-test-font-lock + "fn foo() { let bar = 1; }" + '("fn" font-lock-keyword-face + "foo" font-lock-function-name-face + "let" font-lock-keyword-face + "bar" font-lock-variable-name-face)) + (rust-test-font-lock + "fn foo() { let mut bar = 1; }" + '("fn" font-lock-keyword-face + "foo" font-lock-function-name-face + "let" font-lock-keyword-face + "mut" font-lock-keyword-face + "bar" font-lock-variable-name-face))) + +(ert-deftest font-lock-ampersand () + (rust-test-font-lock + "f(&a)" + '("&" rust-ampersand-face)) + (rust-test-font-lock + "a && b &&& c" + nil) + (rust-test-font-lock + "&'a v" + '("&" rust-ampersand-face + "a" font-lock-variable-name-face)) + (rust-test-font-lock + "&'static v" + '("&" rust-ampersand-face + "static" font-lock-keyword-face)) + (rust-test-font-lock + "&mut v" + '("&" rust-ampersand-face + "mut" font-lock-keyword-face)) + (rust-test-font-lock + "&f(&x)" + '("&" rust-ampersand-face + "&" rust-ampersand-face)) + (rust-test-font-lock + "fn f(x: &X)" + '("fn" font-lock-keyword-face + "f" font-lock-function-name-face + "x" font-lock-variable-name-face + "&" rust-ampersand-face + "X" font-lock-type-face)) + (rust-test-font-lock + "f(&X{x})" + '("&" rust-ampersand-face + "X" font-lock-type-face)) + (rust-test-font-lock + "let x: &'_ f64 = &1.;" + '("let" font-lock-keyword-face + "x" font-lock-variable-name-face + "&" rust-ampersand-face + "_" font-lock-variable-name-face + "f64" font-lock-type-face + "&" rust-ampersand-face)) + (rust-test-font-lock + "let x = &&1;" + '("let" font-lock-keyword-face + "x" font-lock-variable-name-face + "&&" rust-ampersand-face)) + (rust-test-font-lock + "let x = &*y;" + '("let" font-lock-keyword-face + "x" font-lock-variable-name-face + "&" rust-ampersand-face)) + (rust-test-font-lock + "let x = &::std::f64::consts::PI;" + '("let" font-lock-keyword-face + "x" font-lock-variable-name-face + "&" rust-ampersand-face + "std" font-lock-constant-face + "f64" font-lock-type-face + "consts" font-lock-constant-face + "PI" font-lock-type-face)) + (rust-test-font-lock + "let x = &(1, 2);" + '("let" font-lock-keyword-face + "x" font-lock-variable-name-face + "&" rust-ampersand-face)) + (rust-test-font-lock + "let x = &{1};" + '("let" font-lock-keyword-face + "x" font-lock-variable-name-face + "&" rust-ampersand-face)) + (rust-test-font-lock + "let f = &|x| {x + 1};" + '("let" font-lock-keyword-face + "f" font-lock-variable-name-face + "&" rust-ampersand-face)) + (rust-test-font-lock + "let x: &_ = &1;" + '("let" font-lock-keyword-face + "x" font-lock-variable-name-face + "&" rust-ampersand-face + "&" rust-ampersand-face)) + (rust-test-font-lock + "&[1,2]" + '("&" rust-ampersand-face))) + +(ert-deftest font-lock-if-let-binding () + (rust-test-font-lock + "if let Some(var) = some_var { /* no-op */ }" + '("if" font-lock-keyword-face + "let" font-lock-keyword-face + "Some" font-lock-type-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face))) + +(ert-deftest font-lock-single-quote-character-literal () + (rust-test-font-lock + "fn main() { let ch = '\\''; }" + '("fn" font-lock-keyword-face + "main" font-lock-function-name-face + "let" font-lock-keyword-face + "ch" font-lock-variable-name-face + "'\\''" font-lock-string-face))) + +(ert-deftest font-lock-escaped-double-quote-character-literal () + (rust-test-font-lock + "fn main() { let ch = '\\\"'; }" + '("fn" font-lock-keyword-face + "main" font-lock-function-name-face + "let" font-lock-keyword-face + "ch" font-lock-variable-name-face + "'\\\"'" font-lock-string-face))) + +(ert-deftest font-lock-escaped-backslash-character-literal () + (rust-test-font-lock + "fn main() { let ch = '\\\\'; }" + '("fn" font-lock-keyword-face + "main" font-lock-function-name-face + "let" font-lock-keyword-face + "ch" font-lock-variable-name-face + "'\\\\'" font-lock-string-face))) + +(ert-deftest font-lock-hex-escape-character-literal () + (rust-test-font-lock + "let ch = '\\x1f';" + '("let" font-lock-keyword-face + "ch" font-lock-variable-name-face + "'\\x1f'" font-lock-string-face))) + +(ert-deftest font-lock-unicode-escape-character-literal () + (rust-test-font-lock + "let ch = '\\u{1ffff}';" + '("let" font-lock-keyword-face + "ch" font-lock-variable-name-face + "'\\u{1ffff}'" font-lock-string-face))) + +(ert-deftest font-lock-raw-strings-no-hashes () + (rust-test-font-lock + "r\"No hashes\";" + '("r\"No hashes\"" font-lock-string-face))) + +(ert-deftest font-lock-raw-strings-double-quote () + (rust-test-font-lock + "fn main() { + r#\"With a double quote (\")\"#; +} +" + '("fn" font-lock-keyword-face + "main" font-lock-function-name-face + "r#\"With a double quote (\")\"#" font-lock-string-face))) + +(ert-deftest font-lock-raw-strings-two-hashes () + (rust-test-font-lock + "r##\"With two hashes\"##;" + '("r##\"With two hashes\"##" font-lock-string-face))) + +(ert-deftest font-lock-raw-strings-backslash-at-end () + (rust-test-font-lock + "r\"With a backslash at the end\\\";" + '("r\"With a backslash at the end\\\"" font-lock-string-face))) + +(ert-deftest font-lock-two-raw-strings () + (rust-test-font-lock + "fn main() { + r\"With a backslash at the end\\\"; + r##\"With two hashes\"##; +}" + '("fn" font-lock-keyword-face + "main" font-lock-function-name-face + "r\"With a backslash at the end\\\"" font-lock-string-face + "r##\"With two hashes\"##" font-lock-string-face))) + +(ert-deftest font-lock-raw-string-with-inner-hash () + (rust-test-font-lock + "r##\"I've got an octothorpe (#)\"##; foo()" + '("r##\"I've got an octothorpe (#)\"##" font-lock-string-face))) + +(ert-deftest font-lock-raw-string-with-inner-quote-and-hash () + (rust-test-font-lock + "not_the_string(); r##\"string \"# still same string\"##; not_the_string()" + '("r##\"string \"# still same string\"##" font-lock-string-face))) + +(ert-deftest font-lock-string-ending-with-r-not-raw-string () + (rust-test-font-lock + "fn f() { + \"Er\"; +} + +fn g() { + \"xs\"; +}" + '("fn" font-lock-keyword-face + "f" font-lock-function-name-face + "\"Er\"" font-lock-string-face + "fn" font-lock-keyword-face + "g" font-lock-function-name-face + "\"xs\"" font-lock-string-face))) + +(ert-deftest font-lock-string-ending-with-r-word-boundary () + (with-temp-buffer + (rust-mode) + (insert "const foo = \"foo bar\"") + (font-lock-flush) + (font-lock-ensure) + ;; right-word should move the point to the end of the words. + (goto-char 14) + (right-word) + (should (equal 17 (point))) + (right-word) + (should (equal 21 (point))) + )) + +(ert-deftest font-lock-raw-string-trick-ending-followed-by-string-with-quote () + (rust-test-font-lock + "r\"With what looks like the start of a raw string at the end r#\"; +not_a_string(); +r##\"With \"embedded\" quote \"##;" + '("r\"With what looks like the start of a raw string at the end r#\"" font-lock-string-face + "r##\"With \"embedded\" quote \"##" font-lock-string-face))) + +(ert-deftest font-lock-raw-string-starter-inside-raw-string () + ;; Check that it won't look for a raw string beginning inside another raw string. + (rust-test-font-lock + "r#\"In the first string r\" in the first string \"#; +not_in_a_string(); +r##\"In the second string\"##;" + '("r#\"In the first string r\" in the first string \"#" font-lock-string-face + "r##\"In the second string\"##" font-lock-string-face))) + +(ert-deftest font-lock-raw-string-starter-inside-comment () + ;; Check that it won't look for a raw string beginning inside another raw string. + (rust-test-font-lock + "// r\" this is a comment +\"this is a string\"; +this_is_not_a_string();)" + '("// " font-lock-comment-delimiter-face + "r\" this is a comment\n" font-lock-comment-face + "\"this is a string\"" font-lock-string-face))) + +(ert-deftest font-lock-runaway-raw-string () + (rust-test-font-lock + "const Z = r#\"my raw string\";\n// oops this is still in the string" + '("const" font-lock-keyword-face + "Z" font-lock-type-face + "r#\"my raw string\";\n// oops this is still in the string" font-lock-string-face)) + ) + +(ert-deftest font-lock-recognize-closing-raw-string () + (with-temp-buffer + (rust-mode) + (insert "const foo = r##\" +1...............................................50 +1...............................................50 +1...............................................50 +1...............195-->\"; let ...................50 +1...............................................50 +1...............................................50 +1...............................................50 +1...............................................50 +1...............................................50 +1......................500......................50 +\"#; +") + (font-lock-flush) + (font-lock-ensure) + (goto-char 530) + (insert "#") + ;; We have now closed the raw string. Check that the whole string is + ;; recognized after the change + (font-lock-after-change-function (1- (point)) (point) 0) + (should (equal 'font-lock-string-face (get-text-property 195 'face))) ;; The "let" + (should (equal 'font-lock-string-face (get-text-property 500 'face))) ;; The "500" + (should (equal nil (get-text-property 531 'face))) ;; The second ";" + )) + +;;; Documentation comments + +(ert-deftest font-lock-doc-line-comment-parent () + (rust-test-font-lock + "//! doc" + '("//! doc" font-lock-doc-face))) + +(ert-deftest font-lock-doc-line-comment-item () + (rust-test-font-lock + "/// doc" + '("/// doc" font-lock-doc-face))) + +(ert-deftest font-lock-nondoc-line () + (rust-test-font-lock + "////// doc" + '("////// " font-lock-comment-delimiter-face + "doc" font-lock-comment-face))) + +(ert-deftest font-lock-doc-line-in-string () + (rust-test-font-lock + "\"/// doc\"" + '("\"/// doc\"" font-lock-string-face)) + + (rust-test-font-lock + "\"//! doc\"" + '("\"//! doc\"" font-lock-string-face))) + +(ert-deftest font-lock-doc-line-in-nested-comment () + (rust-test-font-lock + "/* /// doc */" + '("/* " font-lock-comment-delimiter-face + "/// doc */" font-lock-comment-face)) + + (rust-test-font-lock + "/* //! doc */" + '("/* " font-lock-comment-delimiter-face + "//! doc */" font-lock-comment-face))) + + +(ert-deftest font-lock-doc-block-comment-parent () + (rust-test-font-lock + "/*! doc */" + '("/*! doc */" font-lock-doc-face))) + +(ert-deftest font-lock-doc-block-comment-item () + (rust-test-font-lock + "/** doc */" + '("/** doc */" font-lock-doc-face))) + +(ert-deftest font-lock-nondoc-block-comment-item () + (rust-test-font-lock + "/***** doc */" + '("/**" font-lock-comment-delimiter-face + "*** doc */" font-lock-comment-face))) + +(ert-deftest font-lock-doc-block-in-string () + (rust-test-font-lock + "\"/** doc */\"" + '("\"/** doc */\"" font-lock-string-face)) + (rust-test-font-lock + "\"/*! doc */\"" + '("\"/*! doc */\"" font-lock-string-face))) + +(ert-deftest font-lock-module-def () + (rust-test-font-lock + "mod foo;" + '("mod" font-lock-keyword-face + "foo" font-lock-constant-face))) + +(ert-deftest font-lock-module-use () + (rust-test-font-lock + "use foo;" + '("use" font-lock-keyword-face + "foo" font-lock-constant-face))) + +(ert-deftest font-lock-module-path () + (rust-test-font-lock + "foo::bar" + '("foo" font-lock-constant-face))) + +(ert-deftest font-lock-submodule-path () + (rust-test-font-lock + "foo::bar::baz" + '("foo" font-lock-constant-face + "bar" font-lock-constant-face))) + +(ert-deftest font-lock-type () + (rust-test-font-lock + "foo::Bar::baz" + '("foo" font-lock-constant-face + "Bar" font-lock-type-face))) + +(ert-deftest font-lock-type-annotation () + "Ensure type annotations are not confused with modules." + (rust-test-font-lock + "parse::();" + ;; Only the i32 should have been highlighted. + '("i32" font-lock-type-face)) + (rust-test-font-lock + "foo:: " + ;; Only the i32 should have been highlighted. + '("i32" font-lock-type-face))) + +(ert-deftest font-lock-question-mark () + "Ensure question mark operator is highlighted." + (rust-test-font-lock + "?" + '("?" rust-question-mark)) + (rust-test-font-lock + "foo\(\)?;" + '("?" rust-question-mark)) + (rust-test-font-lock + "foo\(bar\(\)?\);" + '("?" rust-question-mark)) + (rust-test-font-lock + "\"?\"" + '("\"?\"" font-lock-string-face)) + (rust-test-font-lock + "foo\(\"?\"\);" + '("\"?\"" font-lock-string-face)) + (rust-test-font-lock + "// ?" + '("// " font-lock-comment-delimiter-face + "?" font-lock-comment-face)) + (rust-test-font-lock + "/// ?" + '("/// ?" font-lock-doc-face)) + (rust-test-font-lock + "foo\(\"?\"\);" + '("\"?\"" font-lock-string-face)) + (rust-test-font-lock + "foo\(\"?\"\)?;" + '("\"?\"" font-lock-string-face + "?" rust-question-mark))) + +(ert-deftest rust-test-default-context-sensitive () + (rust-test-font-lock + "let default = 7; impl foo { default fn f() { } }" + '("let" font-lock-keyword-face + "default" font-lock-variable-name-face + "impl" font-lock-keyword-face + "default" font-lock-keyword-face + "fn" font-lock-keyword-face + "f" font-lock-function-name-face))) + +(ert-deftest rust-test-union-context-sensitive () + (rust-test-font-lock + "let union = 7; union foo { x: &'union bar }" + '("let" font-lock-keyword-face + ;; The first union is a variable name. + "union" font-lock-variable-name-face + ;; The second union is a contextual keyword. + "union" font-lock-keyword-face + "foo" font-lock-type-face + "x" font-lock-variable-name-face + ;; This union is the name of a lifetime. + "&" rust-ampersand-face + "union" font-lock-variable-name-face + "bar" font-lock-type-face))) + +(ert-deftest indent-method-chains-no-align () + (let ((rust-indent-method-chain nil)) (test-indent + " +fn main() { + let x = thing.do_it() + .aligned() + .more_alignment(); +} +" + ))) + +(ert-deftest indent-method-chains-no-align-with-question-mark-operator () + (let ((rust-indent-method-chain nil)) (test-indent + " +fn main() { + let x = thing.do_it() + .aligned() + .more_alignment()? + .more_alignment(); +} +" + ))) + +(ert-deftest indent-method-chains-with-align () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { + let x = thing.do_it() + .aligned() + .more_alignment(); +} +" + ))) + +(ert-deftest indent-method-chains-with-align-with-question-mark-operator () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { + let x = thing.do_it() + .aligned() + .more_alignment()? + .more_alignment(); +} +" + ))) + +(ert-deftest indent-method-chains-with-align-and-second-stmt () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { + let x = thing.do_it() + .aligned() + .more_alignment(); + foo.bar(); +} +" + ))) + +(ert-deftest indent-method-chains-field () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { + let x = thing.do_it + .aligned + .more_alignment(); +} +" + ))) + +(ert-deftest indent-method-chains-double-field-on-first-line () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { + let x = thing.a.do_it + .aligned + .more_alignment(); +} +" + ))) + +(ert-deftest indent-method-chains-no-let () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { + thing.a.do_it + .aligned + .more_alignment(); +} +" + ))) + +(ert-deftest indent-method-chains-look-over-comment () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { + thing.a.do_it + // A comment + .aligned + // Another comment + .more_alignment(); +} +" + ))) + +(ert-deftest indent-method-chains-comment () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { + // thing.do_it() + // .aligned() +} +" + ))) + +(ert-deftest indent-method-chains-close-block () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { + foo.bar() +} +" + ))) + +(ert-deftest indent-method-chains-after-comment () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { // comment here should not push next line out + foo.bar() +} +" + ))) + +(ert-deftest indent-method-chains-after-comment2 () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { + // Lorem ipsum lorem ipsum lorem ipsum lorem.ipsum + foo.bar() +} +" + ))) + +(ert-deftest indent-function-after-where () + (let ((rust-indent-method-chain t)) (test-indent + " +fn each_split_within<'a, F>(ss: &'a str, lim: usize, mut it: F) + -> bool where F: FnMut(&'a str) -> bool { +} + +#[test] +fn test_split_within() { +} +" + ))) + +(ert-deftest indent-function-after-where-nested () + (let ((rust-indent-method-chain t)) (test-indent + " +fn outer() { + fn each_split_within<'a, F>(ss: &'a str, lim: usize, mut it: F) + -> bool where F: FnMut(&'a str) -> bool { + } + #[test] + fn test_split_within() { + } + fn bar() { + } +} +" + ))) + +(ert-deftest test-for-issue-36-syntax-corrupted-state () + "This is a test for a issue #36, which involved emacs's +internal state getting corrupted when actions were done in a +specific sequence. The test seems arbitrary, and is, but it was +not clear how to narrow it down further. + +The cause of the bug was code that used to set +`syntax-begin-function' to `beginning-of-defun', which doesn't +actually fulfill the expectations--`syntax-begin-function' is +supposed to back out of all parens, but `beginning-of-defun' +could leave it inside parens if a fn appears inside them. + +Having said that, as I write this I don't understand fully what +internal state was corrupted and how. There wasn't an obvious +pattern to what did and did not trip it." + + ;; When bug #36 was present, the following test would pass, but running it + ;; caused some unknown emacs state to be corrupted such that the following + ;; test failed. Both the "blank_line" and "indented_closing_brace" functions + ;; were needed to expose the error, for instance--deleting either of them + ;; would make the failure go away. + (with-temp-buffer + (rust-mode) + (insert "fn blank_line(arg:i32) -> bool { + +} + +fn indenting_closing_brace() { + if(true) { +} +} + +fn indented_already() { + \n // The previous line already has its spaces +} +") + (font-lock-flush) + (font-lock-ensure) + (goto-char (point-min)) + (forward-line 10) + (move-to-column 0) + (indent-for-tab-command) + (should (equal (current-column) 4)) + ) + + ;; This is the test that would fail only after running the previous one. The + ;; code is extracted from src/libstd/collections/table.rs in the rust tree. + ;; It was not clear how to reduce it further--removing various bits of it + ;; would make it no longer fail. In particular, changing only the comment at + ;; the top of the "next" function was sufficient to make it no longer fail. + (test-indent + " +impl Foo for Bar { + + /// Modifies the bucket pointer in place to make it point to the next slot. + pub fn next(&mut self) { + // Branchless bucket + // As we reach the end of the table... + // We take the current idx: 0111111b + // Xor it by its increment: ^ 1000000b + // ------------ + // 1111111b + // Then AND with the capacity: & 1000000b + // ------------ + // to get the backwards offset: 1000000b + let maybe_wraparound_dist = (self.idx ^ (self.idx + 1)) & self.table.capacity(); + // Finally, we obtain the offset 1 or the offset -cap + 1. + let dist = 1 - (maybe_wraparound_dist as isize); + + self.idx += 1; + + unsafe { + self.raw = self.raw.offset(dist); + } + } + + /// Reads a bucket at a given index, returning an enum indicating whether + /// the appropriate types to call most of the other functions in + /// this module. + pub fn peek(self) { + match foo { + EMPTY_BUCKET => + Empty(EmptyBucket { + raw: self.raw, + idx: self.idx, + table: self.table + }), + _ => + Full(FullBucket { + raw: self.raw, + idx: self.idx, + table: self.table + }) + } + } +} +" + )) + +(ert-deftest test-indent-string-with-eol-backslash () + (test-indent + " +pub fn foo() { + format!(\"abc \\ + def\") +} +" + )) + +(ert-deftest test-indent-string-with-eol-backslash-at-start () + (test-indent + " +pub fn foo() { + format!(\"\\ + abc \\ + def\") +} +" + )) + +(ert-deftest test-indent-string-without-eol-backslash-indent-is-not-touched () + (test-indent + " +pub fn foo() { + format!(\" +abc +def\"); +} + +pub fn foo() { + format!(\"la la la +la +la la\"); +} +" + ;; Should still indent the code parts but leave the string internals alone: + " + pub fn foo() { + format!(\" +abc +def\"); +} + +pub fn foo() { + format!(\"la la la +la +la la\"); + } +" + )) + +(ert-deftest test-indent-string-eol-backslash-mixed-with-literal-eol () + (test-indent + " +fn foo() { + println!(\" +Here is the beginning of the string + and here is a line that is arbitrarily indented \\ + and a continuation of that indented line + and another arbitrary indentation + still another + yet another \\ + with a line continuing it +And another line not indented +\") +} +" + " +fn foo() { + println!(\" +Here is the beginning of the string + and here is a line that is arbitrarily indented \\ + and a continuation of that indented line + and another arbitrary indentation + still another + yet another \\ +with a line continuing it +And another line not indented +\") +} +")) + +(ert-deftest test-indent-string-eol-backslash-dont-touch-raw-strings () + (test-indent + " +pub fn foo() { + format!(r\"\ +abc\ + def\"); +} + +pub fn foo() { + format!(r\"la la la + la\ +la la\"); +} +" + ;; Should still indent the code parts but leave the string internals alone: + " + pub fn foo() { + format!(r\"\ +abc\ + def\"); +} + +pub fn foo() { + format!(r\"la la la + la\ +la la\"); +} +" + )) + +(ert-deftest indent-inside-string-first-line () + (test-indent + ;; Needs to leave 1 space before "world" + "\"hello \\\n world\"")) + +(ert-deftest indent-multi-line-type-param-list () + (test-indent + " +pub fn foo() { + hello(); +}")) + +(ert-deftest indent-open-paren-in-column0 () + ;; Just pass the same text for the "deindented" argument. This + ;; avoids the extra spaces normally inserted, which would mess up + ;; the test because string contents aren't touched by reindentation. + (let ((text " +const a: &'static str = r#\" +{}\"#; +fn main() { + let b = \"//\"; + let c = \"\"; + +} +")) + (test-indent text text))) + +(ert-deftest indent-question-mark-operator () + (test-indent "fn foo() { + if bar()? < 1 { + } + baz(); +}")) + +;; Regression test for #212. +(ert-deftest indent-left-shift () + (test-indent " +fn main() { + let a = [[0u32, 0u32]; 1]; + let i = 0; + let x = a[i][(1 < i)]; + let x = a[i][(1 << i)]; +} +")) + +(defun rust-test-matching-parens (content pairs &optional nonparen-positions) + "Assert that in rust-mode, given a buffer with the given `content', + emacs's paren matching will find all of the pairs of positions + as matching braces. The list of nonparen-positions asserts + specific positions that should NOT be considered to be + parens/braces of any kind. + + This does not assert that the `pairs' list is + comprehensive--there can be additional pairs that don't appear + in the list and the test still passes (as long as none of their + positions appear in `nonparen-positions'.)" + (with-temp-buffer + (rust-mode) + (insert content) + (font-lock-flush) + (font-lock-ensure) + (dolist (pair pairs) + (let* ((open-pos (nth 0 pair)) + (close-pos (nth 1 pair))) + (should (equal 4 (syntax-class (syntax-after open-pos)))) + (should (equal 5 (syntax-class (syntax-after close-pos)))) + (should (equal (scan-sexps open-pos 1) (+ 1 close-pos))) + (should (equal (scan-sexps (+ 1 close-pos) -1) open-pos)))) + (dolist (nonpar-pos nonparen-positions) + (let ((nonpar-syntax-class (syntax-class (syntax-after nonpar-pos)))) + (should-not (equal 4 nonpar-syntax-class)) + (should-not (equal 5 nonpar-syntax-class)))))) + +(ert-deftest rust-test-unmatched-single-quote-in-comment-paren-matching () + ;; This was a bug from the char quote handling that affected the paren + ;; matching. An unmatched quote char in a comment caused the problems. + (rust-test-matching-parens + "// If this appeared first in the file... +\"\\ +{\"; + +// And the { was not the on the first column: + { + // This then messed up the paren matching: '\\' +} + +" + '((97 150) ;; The { and } at the bottom + ))) + +(ert-deftest rust-test-two-character-quotes-in-a-row () + (with-temp-buffer + (rust-mode) + (font-lock-flush) + (font-lock-ensure) + (insert "'\\n','a', fn") + (font-lock-after-change-function 1 12 0) + + (should (equal 'font-lock-string-face (get-text-property 3 'face))) + (should (equal nil (get-text-property 5 'face))) + (should (equal 'font-lock-string-face (get-text-property 7 'face))) + (should (equal nil (get-text-property 9 'face))) + (should (equal 'font-lock-keyword-face (get-text-property 12 'face))) + ) + ) + +(ert-deftest single-quote-null-char () + (rust-test-font-lock + "'\\0' 'a' fn" + '("'\\0'" font-lock-string-face + "'a'" font-lock-string-face + "fn" font-lock-keyword-face))) + +(ert-deftest r-in-string-after-single-quoted-double-quote () + (rust-test-font-lock + "'\"';\n\"r\";\n\"oops\";" + '("'\"'" font-lock-string-face + "\"r\"" font-lock-string-face + "\"oops\"" font-lock-string-face + ))) + +(ert-deftest char-literal-after-quote-in-raw-string () + (rust-test-font-lock + "r#\"\"\"#;\n'q'" + '("r#\"\"\"#" font-lock-string-face + "'q'" font-lock-string-face))) + +(ert-deftest rust-macro-font-lock () + (rust-test-font-lock + "foo!\(\);" + '("foo!" font-lock-preprocessor-face)) + (rust-test-font-lock + "foo!{};" + '("foo!" font-lock-preprocessor-face)) + (rust-test-font-lock + "foo![];" + '("foo!" font-lock-preprocessor-face))) + +(ert-deftest rust-string-interpolation-matcher-works () + (dolist (test '(("print!\(\"\"\)" 9 11 nil) + ("print!\(\"abcd\"\)" 9 15 nil) + ("print!\(\"abcd {{}}\"\);" 9 19 nil) + ("print!\(\"abcd {{\"\);" 9 18 nil) + ("print!\(\"abcd {}\"\);" 9 18 ((14 16))) + ("print!\(\"abcd {{{}\"\);" 9 20 ((16 18))) + ("print!\(\"abcd {}{{\"\);" 9 20 ((14 16))) + ("print!\(\"abcd {} {{\"\);" 9 21 ((14 16))) + ("print!\(\"abcd {}}}\"\);" 9 20 ((14 16))) + ("print!\(\"abcd {{{}}}\"\);" 9 20 ((16 18))) + ("print!\(\"abcd {0}\"\);" 9 18 ((14 17))) + ("print!\(\"abcd {0} efgh\"\);" 9 23 ((14 17))) + ("print!\(\"{1} abcd {0} efgh\"\);" 9 27 ((9 12) (18 21))) + ("print!\(\"{{{1} abcd }} {0}}} {{efgh}}\"\);" 9 33 ((11 14) (23 26))))) + (cl-destructuring-bind (text cursor limit matches) test + (with-temp-buffer + ;; make sure we have a clean slate + (save-match-data + (set-match-data nil) + (insert text) + (goto-char cursor) + (if (null matches) + (should (equal (progn + (rust-string-interpolation-matcher limit) + (match-data)) + nil)) + (dolist (pair matches) + (rust-string-interpolation-matcher limit) + (should (equal (match-beginning 0) (car pair))) + (should (equal (match-end 0) (cadr pair)))))))))) + +(ert-deftest rust-formatting-macro-font-lock () + ;; test that the block delimiters aren't highlighted and the comment + ;; is ignored + (rust-test-font-lock + "print!(\"\"); { /* print!(\"\"); */ }" + '("print!" rust-builtin-formatting-macro + "\"\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "print!(\"\"); */" font-lock-comment-face)) + ;; with newline directly following delimiter + (rust-test-font-lock + "print!(\n\"\"\n); { /* print!(\"\"); */ }" + '("print!" rust-builtin-formatting-macro + "\"\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "print!(\"\"); */" font-lock-comment-face)) + ;; with empty println!() + (rust-test-font-lock + "println!(); { /* println!(); */ }" + '("println!" rust-builtin-formatting-macro + "/* " font-lock-comment-delimiter-face + "println!(); */" font-lock-comment-face)) + ;; other delimiters + (rust-test-font-lock + "print!{\"\"}; { /* no-op */ }" + '("print!" rust-builtin-formatting-macro + "\"\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; other delimiters + (rust-test-font-lock + "print![\"\"]; { /* no-op */ }" + '("print!" rust-builtin-formatting-macro + "\"\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; no interpolation + (rust-test-font-lock + "print!(\"abcd\"); { /* no-op */ }" + '("print!" rust-builtin-formatting-macro + "\"abcd\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; only interpolation + (rust-test-font-lock + "print!(\"{}\"); { /* no-op */ }" + '("print!" rust-builtin-formatting-macro + "\"" font-lock-string-face + "{}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; text + interpolation + (rust-test-font-lock + "print!(\"abcd {}\", foo); { /* no-op */ }" + '("print!" rust-builtin-formatting-macro + "\"abcd " font-lock-string-face + "{}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; text + interpolation with specification + (rust-test-font-lock + "print!(\"abcd {0}\", foo); { /* no-op */ }" + '("print!" rust-builtin-formatting-macro + "\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; text + interpolation with specification and escape + (rust-test-font-lock + "print!(\"abcd {0}}}\", foo); { /* no-op */ }" + '("print!" rust-builtin-formatting-macro + "\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + "}}\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; multiple pairs + (rust-test-font-lock + "print!(\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }" + '("print!" rust-builtin-formatting-macro + "\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + " efgh " font-lock-string-face + "{1}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; println + (rust-test-font-lock + "println!(\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }" + '("println!" rust-builtin-formatting-macro + "\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + " efgh " font-lock-string-face + "{1}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; eprint + (rust-test-font-lock + "eprint!(\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }" + '("eprint!" rust-builtin-formatting-macro + "\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + " efgh " font-lock-string-face + "{1}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; eprintln + (rust-test-font-lock + "eprintln!(\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }" + '("eprintln!" rust-builtin-formatting-macro + "\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + " efgh " font-lock-string-face + "{1}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; format + (rust-test-font-lock + "format!(\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }" + '("format!" rust-builtin-formatting-macro + "\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + " efgh " font-lock-string-face + "{1}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; print + raw string + (rust-test-font-lock + "format!(r\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }" + '("format!" rust-builtin-formatting-macro + "r\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + " efgh " font-lock-string-face + "{1}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; print + raw string with hash + (rust-test-font-lock + "format!(r#\"abcd {0} efgh {1}\"#, foo, bar); { /* no-op */ }" + '("format!" rust-builtin-formatting-macro + "r#\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + " efgh " font-lock-string-face + "{1}" rust-string-interpolation + "\"#" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; print + raw string with two hashes + (rust-test-font-lock + "format!(r##\"abcd {0} efgh {1}\"##, foo, bar); { /* no-op */ }" + '("format!" rust-builtin-formatting-macro + "r##\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + " efgh " font-lock-string-face + "{1}" rust-string-interpolation + "\"##" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face))) + +(ert-deftest rust-write-macro-font-lock () + (rust-test-font-lock + "write!(f, \"abcd {0}}} efgh {1}\", foo, bar); { /* no-op */ }" + '("write!" rust-builtin-formatting-macro + "\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + "}} efgh " font-lock-string-face + "{1}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + (rust-test-font-lock + "writeln!(f, \"abcd {0}}} efgh {1}\", foo, bar); { /* no-op */ }" + '("writeln!" rust-builtin-formatting-macro + "\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + "}} efgh " font-lock-string-face + "{1}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + (rust-test-font-lock + "println!(\"123\"); eprintln!(\"123\"); cprintln!(\"123\");" + '("println!" rust-builtin-formatting-macro + "\"123\"" font-lock-string-face + "eprintln!" rust-builtin-formatting-macro + "\"123\"" font-lock-string-face + "cprintln!" font-lock-preprocessor-face + "\"123\"" font-lock-string-face))) + +(ert-deftest font-lock-fontify-angle-brackets () + "Test that angle bracket fontify" + (should (equal (rust-test-fontify-string "<>") "<>")) + (should (equal (rust-test-fontify-string "") "")) + (should (equal (rust-test-fontify-string "<<>>") "<<>>")) + (should (equal (rust-test-fontify-string "<>>") "<>>")) + (should (equal (rust-test-fontify-string "<<>") "<<>"))) + +(ert-deftest rust-test-basic-paren-matching () + (rust-test-matching-parens + " +fn foo() { + let a = [1, 2, 3]; +}" + '((8 9) ;; Parens of foo() + (11 36) ;; Curly braces + (25 33) ;; Square brackets + ))) + +(ert-deftest rust-test-paren-matching-generic-fn () + (rust-test-matching-parens + " +fn foo() { +}" + '((8 10) ;; Angle brackets + (11 12) ;; Parens + (14 16) ;; Curly braces + ))) + +(ert-deftest rust-test-paren-matching-generic-fn-with-return-value () + (rust-test-matching-parens + " +fn foo() -> bool { + false +}" + '((8 10) ;; Angle brackets + (11 12) ;; Parens + (22 34) ;; Curly braces + ) + + '(15 ;; The ">" in "->" is not an angle bracket + ))) + +(ert-deftest rust-test-paren-matching-match-stmt () + (rust-test-matching-parens + " +fn foo() { + something_str(match ::method() { + Some(_) => \"Got some\", + None => \"Nada\" + }); +}" + '((8 9) ;; parens of fn foo + (11 127) ;; curly braces of foo + (30 124) ;; parens of something_str + (37 51) ;; angle brackets of + (60 61) ;; parens of method() + (63 123) ;; curly braces of match + (77 79) ;; parens of Some(_) + ) + + '(82 ;; > in first => + 112 ;; > in second => + ))) + +(ert-deftest rust-test-paren-matching-bitshift-operators () + (rust-test-matching-parens + " +fn foo(z:i32) { + let a:Option> = Some(Ok(4 >> 1)); + let b = a.map(|x| x.map(|y| y << 3)); + let trick_question = z<<::method(); // First two + ) + '(64 ;; The >> inside Some(Ok()) are not angle brackets + 65 ;; The >> inside Some(Ok()) are not angle brackets + 106 ;; The << inside map() are not angle brackets + 107 ;; The << inside map() are not angle brackets + 140 ;; The << before are not angle brackets + 141 ;; The << before are not angle brackets + 183 ;; The < inside the comment + ))) + +(ert-deftest rust-test-paren-matching-angle-bracket-after-colon-ident () + (rust-test-matching-parens + " +struct Bla { + a:Option<(i32,Option)>, + b:Option, + c:bool +} + +fn f(x:i32,y:Option) { + let z:Option = None; + let b:Bla = Bla{ + a:None, + b:None, + c:x + (30 49) ;; Outer angle brackets of a:Option<...> + (42 47) ;; Inner angle brackets of Option + (64 66) ;; Angle brackets of Option + (102 106) ;; Angle brackets of y:Option + (127 131) ;; Angle brackets of z:Option + (154 157) ;; Angle brackets of b:Bla + ) + '(209 ;; less than operator in c:x Bar { + Bar { + b:x<3 + } +}" + '() + '(17 ;; the -> is not a brace + 46 ;; x<3 the < is a less than sign + )) + ) + +(ert-deftest rust-test-paren-matching-nested-struct-literals () + (rust-test-matching-parens + " +fn f(x:i32,y:i32) -> Foo { + Foo{ + bar:Bar{ + a:3, + b:x + ) + '(92 ;; less than operator x X>() -> Z { +} +" + '((8 23) ;; The angle brackets of foo + (20 22 ;; The angle brackets of X + )) + '(17 ;; The first -> + 28 ;; The second -> + ) + )) + +(ert-deftest rust-test-paren-matching-lt-ops-in-fn-params () + (rust-test-matching-parens + " +fn foo(x:i32) { + f(x < 3); +} +" + '() + '(26 ;; The < inside f is a less than operator + ) + )) + +(ert-deftest rust-test-paren-matching-lt-ops-in-fn-params () + (rust-test-matching-parens + " +fn foo(x:i32) -> bool { + return x < 3; +} +" + '() + '(17 ;; The -> + 39 ;; The < after return is a less than operator + ) + )) + +(ert-deftest rust-test-type-paren-matching-angle-brackets-in-type-items () + (rust-test-matching-parens + " +type Foo = Blah; +type Bar = (Foo, Bletch); +type ThisThing = HereYouGo B>,E>>;" + '((17 21) ;; Angle brackets of Blah + (32 34) ;; Angle brackets of Bar + (50 52) ;; Angle brackets of Bletch + (70 78) ;; Angle brackets of ThisThing + (91 118) ;; Angle brackets of HereYouGo<...> + (95 117) ;; Angle brackets of Y + (106 111) ;; Angle brackets of B> + (108 110) ;; Angle brackets of C + (114 116) ;; Angle brackets of E + ))) + +(ert-deftest rust-test-paren-matching-tuple-like-struct () + (rust-test-matching-parens + " +struct A(Option); +struct C(Result);" + '((17 19) ;; The angle brackets + (10 20) ;; The parens of A(); + (31 33) ;; The angle brackets of C + (41 47) ;; The angle brackets of Result + ) + '())) + +(ert-deftest rust-test-paren-matching-in-enum () + (rust-test-matching-parens + " +enum Boo { + TupleLike(Option), + StructLike{foo: Result} +}" + '((10 12) ;; Angle brackets of Boo + (36 38) ;; Angle brackets of Option + (68 74) ;; Angle brackets of Result + ))) + +(ert-deftest rust-test-paren-matching-assoc-type-bounds () + (rust-test-matching-parens + "impl >> Thing {}" + '((6 29) ;; Outer angle brackets of impl + (10 28) ;; Outer angle brackets of B> + (24 26) ;; Inner angle brackets of C + (36 38) ;; Angle brackets of Thing + ) + )) + +(ert-deftest rust-test-paren-matching-plus-signs-in-expressions-and-bounds () + ;; Note that as I write this, the function "bluh" below does not compile, but + ;; it warns that the equality constraint in a where clause is "not yet + ;; supported." It seems that the compiler will support this eventually, so + ;; the emacs mode needs to support it. + (rust-test-matching-parens + "fn foo,B>(a:A,b:B) -> bool where B:Trait3+Trait4 { + 2 + a < 3 && 3 + b > 11 +} + +fn bluh() where A:Fn()+MyTrait, MyTrait::AssocType = Option { +} + +fn fluh() where C:Fn(i32) -> (i32, i32) + SomeTrait, C::AssocType = OtherThing { +}" + '((7 30) ;; Angle brackets of foo<...> + (23 27) ;; Angle brackets of Trait2 + (63 67) ;; Angle brackets of Trait3 + (75 79) ;; Angle brackets of Trait4 + + (121 123) ;; Angle brackets of bluh + (147 151) ;; Angle brackets of MyTrait + + (161 163) ;; Angle brackets of MyTrait + (184 189) ;; Angle brackets of Option + + (203 205) ;; Angle brackets of + (250 254) ;; Angle brackets of SomeTrait + (282 287) ;; Angle brackets of Option + ) + '(93 ;; Less-than sign of a < 3 + 106 ;; Greater than sign of b > 11 + ))) + +(ert-deftest rust-test-paren-matching-generic-type-in-tuple-return-type () + (rust-test-matching-parens + "pub fn take(mut self) -> (EmptyBucket, K, V) {}" + '((38 46)) + )) + +(ert-deftest rust-test-paren-matching-references-and-logical-and () + (rust-test-matching-parens + " +fn ampersand_check(a: &Option, b:bool) -> &Option { + a.map(|x| { + b && x < 32 + }) +}" + '((31 35) ;; Option + (56 60) ;; Option + ) + '(95 ;; x < 32 + ) + ) + ) + +(ert-deftest rust-test-paren-matching-lt-sign-in-if-statement () + (rust-test-matching-parens + " +fn if_check(a:i32,b:i32,c:i32) { + if a + b < c { + + } + if a < b { + + } + if (c < a) { + + } +} + +fn while_check(x:i32,y:i32) -> bool { + while x < y { + } + for x in y < x { + } + match y < z { + true => (), _ => () + } + return z < y; +}" + '() + '(48 ;; b < c + 78 ;; a < b + 109 ;; (c < a) + + 184 ;; x < y + 211 ;; y < x + 235 ;; y < z + 288 ;; z < y + ))) + +(ert-deftest rust-test-paren-matching-lt-expr-with-field () + (rust-test-matching-parens + "fn foo() { x.y < 3 }" + '() + '(16 ;; x.y < 3 + ))) + +(ert-deftest rust-test-paren-matching-lt-expr-with-quote () + (rust-test-matching-parens + " +fn quote_check() { + 'x' < y; + \"y\" < x; + r##\"z\"## < q; + a <= 3 && b < '2' +}" + '() + '(29 ;; 'x' < y + 42 ;; "y" < x + 60 ;; r##"z"## < q + 71 ;; a <= '3' + 81 ;; b < '2' + ))) + +(ert-deftest rust-test-paren-matching-keywords-capitalized-are-ok-type-names () + (rust-test-matching-parens + " +fn foo() -> Box { + let z:If = If(a < 3); +}" + '((17 21) ;; Box + (37 42) ;; If + ) + '(51 ;; If(a < 3) + ))) + +(ert-deftest rust-test-paren-matching-lt-expression-inside-macro () + (rust-test-matching-parens + "fn bla() { assert!(x < y); }" + '() + '(22 ;; x < y + ))) + +(ert-deftest rust-test-paren-matching-array-types-with-generics () + (rust-test-matching-parens + "fn boo () -> [Option] {}" + '((21 25)))) + +(ert-deftest rust-test-paren-matching-angle-bracket-inner-reference () + (rust-test-matching-parens + "fn x() -> Option<&Node> {}" + '((17 26) ;; Option + (23 25) ;; Node + ))) + +(ert-deftest rust-test-paren-matching-lt-operator-after-semicolon () + (rust-test-matching-parens + "fn f(x:i32) -> bool { (); x < 3 }" + '() + '(29 + ))) + +(ert-deftest rust-test-paren-matching-lt-operator-after-comma () + (rust-test-matching-parens + "fn foo() { + (e, a < b) +}" + '((16 25) ;; The parens () + ) + '(22 ;; The < operator + ))) + +(ert-deftest rust-test-paren-matching-lt-operator-after-let () + (rust-test-matching-parens + "fn main() { + let x = a < b; +}" + '((11 32) ;; The { } + ) + '(27 ;; The < operator + ))) + +(ert-deftest rust-test-paren-matching-two-lt-ops-in-a-row () + (rust-test-matching-parens + "fn next(&mut self) -> Option<::Item>" + '((29 51) ;; Outer Option<> + (30 44) ;; Inner + ) + '(21 + ))) + +(ert-deftest rust-test-paren-matching-lt-after-caret () + (rust-test-matching-parens + "fn foo() { x^2 < 3 }" + '((10 20) ;; The { } + ) + '(16 ;; The < operator + ))) + +(ert-deftest rust-test-paren-matching-lt-operator-after-special-type () + (rust-test-matching-parens + "fn foo() { low as u128 <= c }" + '((10 29)) + '(24))) + +(ert-deftest rust-test-paren-matching-lt-operator-after-closing-curly-brace () + (rust-test-matching-parens + "fn main() { if true {} a < 3 }" + '((11 30) + ) + '(26))) + +(ert-deftest rust-test-paren-matching-const () + (rust-test-matching-parens + " +const BLA = 1 << 3; +const BLUB = 2 < 4;" + '() + '(16 + 17 ;; Both chars of the << in 1 << 3 + 37 ;; The < in 2 < 4 + ))) + +(ert-deftest rust-test-paren-matching-c-like-enum () + (rust-test-matching-parens + " +enum CLikeEnum { + Two = 1 << 1, + Four = 1 << 2 +}" + '((17 56 ;; The { } of the enum + )) + '(31 + 32 ;; The first << + 50 + 51 ;; The second << + ))) + +(ert-deftest rust-test-paren-matching-no-angle-brackets-in-macros () + (rust-test-matching-parens + " +fn foo(a:A) { + macro_a!( foo:: ); + macro_b![ foo as Option ]; +} + +macro_c!{ + struct Boo {} +}" + '((8 10)) + ;; Inside macros, it should not find any angle brackets, even if it normally + ;; would + '(38 ;; macro_a < + 57 ;; macro_a > + 89 ;; macro_b < + 91 ;; macro_b > + 123 ;; macro_c < + 125 ;; macro_d > + ))) + +(ert-deftest rust-test-paren-matching-no-angle-brackets-in-macro-rules () + (rust-test-matching-parens + " +fn foo(a:A) { + macro_rules! foo ( foo:: ); + macro_rules! bar [ foo as Option ]; +} + +macro_c!{ + struct Boo {} +}" + '((8 10)) + ;; Inside macros, it should not find any angle brackets, even if it normally + ;; would + '(47 ;; foo < + 62 ;; foo > + 107 ;; bar < + 109 ;; bar > + 141 ;; macro_c < + 143 ;; macro_c > + ))) + +(ert-deftest rust-test-in-macro-do-not-fail-on-unbalance () + (should + ;; We don't care about the results here, so long as they do not error + (with-temp-buffer + (insert + "fn foo(a:A) { + macro_c!{ + struct Boo {} +") + (rust-mode) + (goto-char (point-max)) + (syntax-ppss)))) + + +(ert-deftest rust-test-in-macro-no-caching () + (should-not + (with-temp-buffer + (insert + "fn foo(a:A) { + macro_c!{ + struct Boo {} +") + (rust-mode) + (search-backward "macro") + ;; do not use the cache + (let ((rust-macro-scopes nil)) + (rust-in-macro))))) + +(ert-deftest rust-test-in-macro-fake-cache () + (should + (with-temp-buffer + (insert + "fn foo(a:A) { + macro_c!{ + struct Boo {} +") + (rust-mode) + (search-backward "macro") + ;; make the cache lie to make the whole buffer in scope + ;; we need to be at paren level 1 for this to work + (let ((rust-macro-scopes `((,(point-min) ,(point-max))))) + (rust-in-macro))))) + +(ert-deftest rust-test-in-macro-broken-cache () + (should-error + (with-temp-buffer + (insert + "fn foo(a:A) { + macro_c!{ + struct Boo {} +") + (rust-mode) + (search-backward "Boo") + ;; do we use the cache at all + (let ((rust-macro-scopes '(I should break))) + (rust-in-macro))))) + +(ert-deftest rust-test-in-macro-nested () + (should + (equal + (with-temp-buffer + (insert + "macro_rules! outer { + () => { vec![] }; +}") + (rust-mode) + (rust-macro-scope (point-min) (point-max))) + '((38 40) (20 45))))) + +(ert-deftest rust-test-in-macro-not-with-space () + (should + (equal + (with-temp-buffer + (insert + "fn foo() { + if !(mem::size_of::() > 8) { + bar() + } +}") + (rust-mode) + (rust-macro-scope (point-min) (point-max))) + 'empty))) + + +(ert-deftest rust-test-paren-matching-type-with-module-name () + (rust-test-matching-parens + " +const X: libc::c_int = 1 << 2; +fn main() { + let z: libc::c_uint = 1 << 4; +} +" + '((43 79)) ;; The curly braces + '(27 + 28 ;; The first << + 73 + 74 ;; The second << + ))) + +(ert-deftest rust-test-paren-matching-qualififed-struct-literal () + (rust-test-matching-parens + " +fn foo() -> Fn(asd) -> F { + let z = foo::Struct{ b: 1 << 4, c: 2 < 4 } +}" + '((30 80) ;; Outer curly brackets + ) + '(62 + 63 ;; The shift operator + 73 ;; The less than operator + ))) + +(ert-deftest rust-test-paren-matching-let-mut () + (rust-test-matching-parens + " +fn f() { + let mut b = 1 < 3; + let mut i = 1 << 3; +} +" + '() + '(28 ;; 1 < 3 + 51 + 52 ;; 1 << 3 + ))) + +(ert-deftest rust-test-paren-matching-as-ref-type () + (rust-test-matching-parens + "fn f() { + let a = b as &Foo; +}" + '((31 35) ;; Angle brackets Foo + ))) + +(ert-deftest rust-test-paren-matching-type-ascription () + (rust-test-matching-parens + " +fn rfc803() { + let z = a < b:FunnkyThing; + let s = Foo { + a: b < 3, + b: d:CrazyStuff < 3, + c: 2 < x:CrazyStuff + } +}" + '((45 49) ;; FunkyThing + (111 115) ;; CrazyStuff + (149 154) ;; CrazyStuff + ) + '(30 ;; a < b + 83 ;; b < 3 + 117 ;; d... < 3 + 135 ;; 2 < x + ))) + +(ert-deftest rust-test-paren-matching-angle-brackets-in-enum-with-where-claause () + (rust-test-matching-parens + " +enum MyEnum where T:std::fmt::Debug { + Thing(Option) +}" + '((13 15) ;; MyEnum + (59 61) ;; Option + ))) + +(ert-deftest rust-test-paren-matching-where-clauses-with-closure-types () + (rust-test-matching-parens + " +enum Boo<'a,T> where T:Fn() -> Option<&'a str> + 'a { + Thingy(Option<&'a T>) +} + +fn foo<'a>() -> Result where C::X: D, B:FnMut() -> Option+'a { + Foo(a < b) +} + +type Foo where T: Copy = Box; +" + '((10 15) ;; Boo<'a,T> + (39 47) ;; Option<&'a str> + (72 78) ;; Option<&'a T> + + (106 110) ;; Result + (125 127) ;; D + (149 151) ;; Option + (184 186) ;; Foo + (207 209) ;; Box + ) + + '(168 ;; Foo(a < b) + ) + )) + +(ert-deftest rust-test-angle-bracket-matching-turned-off () + (let ((rust-match-angle-brackets nil)) + (rust-test-matching-parens + "fn foo() {}" + '((10 11)) + '(7 9)))) + + +(ert-deftest redo-syntax-after-change-far-from-point () + (let* + ((tmp-file-name (make-temp-file "rust-mdoe-test-issue104")) + (base-contents (apply #'concat (append '("fn foo() {\n\n}\n") (make-list 500 "// More stuff...\n") '("fn bar() {\n\n}\n"))))) + ;; Create the temp file... + (with-temp-file tmp-file-name + (insert base-contents)) + (with-temp-buffer + (insert-file-contents tmp-file-name 'VISIT nil nil 'REPLACE) + (rust-mode) + (goto-char (point-max)) + (should (= 0 (rust-paren-level))) + (with-temp-file tmp-file-name + (insert base-contents) + (goto-char 12) ;; On the blank line in the middle of fn foo + (insert " let z = 1 < 3;") + ) + (revert-buffer 'IGNORE-AUTO 'NOCONFIRM 'PRESERVE-MODES) + (should (= 0 (rust-paren-level))) + ) + ) + ) + +(defun test-imenu (code expected-items) + (with-temp-buffer + (rust-mode) + (insert code) + (let ((actual-items + ;; Replace ("item" . #() { +} + +pub ( in self::super ) fn f9() { +} + +pub ( in super ) fn f10() { +} + +pub(in crate) fn f11() { +} + +pub (in self) fn f12() { +} +" + '(("Fn" + "f1" + "f2" + "f3" + "f4" + "f5" + "f6" + "f7" + "f8" + "f9" + "f10" + "f11" + "f12")))) + +(ert-deftest rust-test-imenu-impl-with-lifetime () + (test-imenu + " +impl<'a> One<'a> { + fn one() {} +} + +impl Two<'a> { + fn two() {} +} +" + '(("Impl" "One" "Two") + ("Fn" "one" "two")))) + +(ert-deftest font-lock-function-parameters () + (rust-test-font-lock + "fn foo(a: u32, b : u32) {}" + '("fn" font-lock-keyword-face + "foo" font-lock-function-name-face + "a" font-lock-variable-name-face + "u32" font-lock-type-face + "b" font-lock-variable-name-face + "u32" font-lock-type-face))) + +(ert-deftest variable-in-for-loop () + (rust-test-font-lock + "for var in iter" + '("for" font-lock-keyword-face + "var" font-lock-variable-name-face + "in" font-lock-keyword-face)) + (rust-test-font-lock + "for Foo{var} in iter" + '("for" font-lock-keyword-face + "Foo" font-lock-type-face + "in" font-lock-keyword-face))) + +(ert-deftest rust-test-dbg-wrap-symbol () + (rust-test-manip-code + "let x = add(first, second);" + 15 + #'rust-dbg-wrap-or-unwrap + "let x = add(dbg!(first), second);")) + +(ert-deftest rust-test-dbg-wrap-symbol-unbalanced () + (rust-test-manip-code + "let x = add((first, second);" + 14 + #'rust-dbg-wrap-or-unwrap + "let x = add((dbg!(first), second);")) + +(ert-deftest rust-test-dbg-wrap-region () + (rust-test-manip-code + "let x = add(first, second);" + 9 + (lambda () + (transient-mark-mode 1) + (push-mark nil t t) + (goto-char 26) + (rust-dbg-wrap-or-unwrap)) + "let x = dbg!(add(first, second));")) + +(defun rust-test-dbg-unwrap (position) + (rust-test-manip-code + "let x = add(dbg!(first), second);" + position + #'rust-dbg-wrap-or-unwrap + "let x = add(first, second);")) + +(ert-deftest rust-test-dbg-uwnrap-within () + (rust-test-dbg-unwrap 19)) + +(ert-deftest rust-test-dbg-uwnrap-on-paren () + (rust-test-dbg-unwrap 17)) + +(ert-deftest rust-test-dbg-uwnrap-on-dbg-middle () + (rust-test-dbg-unwrap 15)) + +(ert-deftest rust-test-dbg-uwnrap-on-dbg-start () + (rust-test-dbg-unwrap 13)) + +(ert-deftest rust-test-dbg-unwrap-inside-string-literal () + (rust-test-manip-code + "let x = \"foo, bar\"";" + 15 + #'rust-dbg-wrap-or-unwrap + "let x = dbg!(\"foo, bar\")")) + +(ert-deftest rust-test-project-located () + (skip-unless (executable-find rust-cargo-bin)) + (let* ((test-dir (expand-file-name "test-project/" default-directory)) + (manifest-file (expand-file-name "Cargo.toml" test-dir))) + (let ((default-directory test-dir)) + (should (equal (expand-file-name (rust-buffer-project)) manifest-file))))) + +(defun rust-collect-matches (spec) + (let ((matches nil)) + (goto-char (point-min)) + (while (re-search-forward (car spec) nil t) + (push + (mapcar (lambda (r) + (let ((match-pos + (nth (cdr r) spec))) + (cond ((and (eq :type (car r)) (consp match-pos)) + (compilation-face match-pos)) + ((eq :type (car r)) + (cdr (assoc match-pos '((1 . compilation-warning) + (0 . compilation-info) + (2 . compilation-error))))) + ((and (null match-pos) (eq :column (car r))) + 'back-to-indentation) + ((and (null match-pos) (eq :file (car r))) + 'like-previous-one) + ((null match-pos) + (error (format "%S" (car r)))) + (t + (match-string match-pos))))) + ;; see compilation-error-regexp-alist + '((:file . 1) + (:line . 2) + (:column . 3) + (:type . 4) + (:mouse-highlight . 5))) + matches)) + (nreverse matches))) + +(ert-deftest compilation-regexp-dashes () + (with-temp-buffer + ;; should match + (insert "error found a -> b\n --> file1.rs:12:34\n\n") + (insert "error[E1234]: found a -> b\n --> file2.rs:12:34\n\n") + (insert "warning found a -> b\n --> file3.rs:12:34\n\n") + (insert "note: `ZZZ` could also refer to the constant imported here -> b\n --> file4.rs:12:34\n\n") + (insert " ::: file5.rs:12:34\n\n") + ;; should not match + (insert "werror found a -> b\n --> no_match.rs:12:34\n\n") + (insert "error[E0061]: this function takes 1 parameter but 2 parameters were supplied\n --> file6.rs:132:34 + | +82 | fn duration_ms_since(time: &Option) -> u128 { + | ------------------------------------------------------- defined here +... +132 | self.total_time_ms = duration_ms_since(&self.program_start, 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +") + (should (equal + '((("file1.rs" "12" "34" compilation-error "file1.rs:12:34") + ("file2.rs" "12" "34" compilation-error "file2.rs:12:34") + ("file3.rs" "12" "34" compilation-warning "file3.rs:12:34") + ("file4.rs" "12" "34" compilation-info "file4.rs:12:34") + ("file6.rs" "132" "34" compilation-error "file6.rs:132:34")) + (("file5.rs" "12" "34" compilation-info "file5.rs:12:34")) + ((like-previous-one "82" back-to-indentation compilation-info "82") + (like-previous-one "132" back-to-indentation compilation-info "132"))) + (mapcar #'rust-collect-matches + (list rustc-compilation-regexps + rustc-colon-compilation-regexps + rustc-refs-compilation-regexps)))))) + +;; If electric-pair-mode is available, load it and run the tests that use it. If not, +;; no error--the tests will be skipped. +(require 'elec-pair nil t) + +;; The emacs 23 and 24 versions of ERT do not have test skipping +;; functionality. So don't even define these tests if elec-pair is +;; not available. +(when (featurep 'elec-pair) + (defun test-electric-pair-insert (original point-pos char closer) + (let ((old-electric-pair-mode electric-pair-mode)) + (electric-pair-mode 1) + (unwind-protect + (with-temp-buffer + (rust-mode) + (insert original) + (font-lock-flush) + (font-lock-ensure) + + (goto-char point-pos) + (deactivate-mark) + (let ((last-command-event char)) (self-insert-command 1)) + (should (equal (char-after) + (or closer (aref original point-pos))))) + (electric-pair-mode (or old-electric-pair-mode 1))))) + + (ert-deftest rust-test-electric-pair-generic-fn () + (test-electric-pair-insert "fn foo() { }" 7 ?< ?>)) + + (ert-deftest rust-test-electric-pair-impl-param () + (test-electric-pair-insert "impl Foo for Bar" 5 ?< ?>)) + + (ert-deftest rust-test-electric-pair-impl-for-type-param () + (test-electric-pair-insert "impl Foo for Bar" 22 ?< ?>)) + + (ert-deftest rust-test-electric-pair-lt-expression () + (test-electric-pair-insert "fn foo(bar:i32) -> bool { bar }" 30 ?< nil)) + + (ert-deftest rust-test-electric-pair-lt-expression-in-struct-literal () + (test-electric-pair-insert "fn foo(x:i32) -> Bar { Bar { a:(bleh() + whatever::()), b:x } }" 63 ?< nil)) + + (ert-deftest rust-test-electric-pair-lt-expression-capitalized-keyword () + (test-electric-pair-insert "fn foo() -> Box" 16 ?< ?>)) + + (ert-deftest rust-test-electric-pair-<-> () + (let ((old-electric-pair-mode electric-pair-mode)) + (electric-pair-mode 1) + (unwind-protect + (with-temp-buffer + (electric-pair-mode 1) + (rust-mode) + (mapc (lambda (c) + (let ((last-command-event c)) (self-insert-command 1))) + "tmp") + (should + (equal "tmp" (buffer-substring-no-properties (point-min) + (point-max))))) + (electric-pair-mode (or old-electric-pair-mode 1)))))) + +;; Ensure the byte compiler knows about this function, even though it’s +;; conditionally-defined. +(declare-function test-electric-pair-insert "rust-mode-tests" + (original point-pos char closer)) + +(ert-deftest rust-mode-map () + (with-temp-buffer + (let (from to match (match-count 0)) + (rust-mode) + (describe-buffer-bindings (current-buffer)) + (goto-char (point-min)) + (re-search-forward "Major Mode Bindings") + (setq from (point)) + (re-search-forward " ") + (setq to (point)) + (goto-char from) + (while (re-search-forward "^C-c.*$" to t) + (setq match-count (1+ match-count)) + (setq match (match-string 0)) + (eval + `(should + (or + (string-match "Prefix Command" ,match) + (string-match "^C-c C" ,match))) + t)) + (should (< 0 match-count))))) -- cgit v1.3