Repository: nuxt/nuxt.js (ts)

Issue:
Fails to serialize certain functions using serializeFunction in the template
<!-- 💚 Thanks for your time to make Nuxt better with your feedbacks 💚

**IMPORTANT** Before reporting a bug:

- Please make sure that you have read through Nuxt documentation: https://nuxtjs.org
- If issue is related to a module please create the issue in corresponding repository
- Ensure using latest version of nuxt dependencies using `yarn upgrade nuxt` or `npm upgrade nuxt`

👍 A properly detailed bug report can save a LOT of time and help fixing issues as soon as possible.
-->

### Versions

- nuxt: v2.14.12
- node: v14

### Reproduction

https://codesandbox.io/s/serializefunction-bug-3elqy?file=/pages/index.vue

<!--
Link to a minimal test case based on one of:
- A fork of https://template.nuxtjs.org
- A GitHub repository that can reproduce the bug
Without a reproduction, it is so hard to address problem :(
-->

### Steps to reproduce

From a template, call
```ejs
<%
const obj = {
  fn () { const x = rule => rule }
};
const serialized = serializeFunction(obj.fn)
%>
<%= serialized %>
```

### What is Expected?

That the serialized function starts with a `function` keyword.


### What is actually happening?

The serialized function starts with a `fn` identifier.

Pull request:
fix(utils): `serializeFunction` fails with certain functions
A function shorthand with certain statements inside failed to
serialize properly using serializeFunction.

Resolves #8779

<!--- Provide a general summary of your changes in the title above -->

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [x] Bug fix (a non-breaking change which fixes an issue)
- [ ] New feature (a non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)


## Description
The issue was due to the replacement patterns not considering there being
a space between the function name and the parenthesis before the
arguments.

Changed the simple string pattern to regexp to be able to match any
number of whitespace characters between those.

Also changed the test file to not be transformed by babel-jest as that
has affected the input passed to "serializeFunction" and made some tests
invalid. For example, the new test didn't even fail with babel-jest as
it was transforming the test input in a way that made it pass.

## Checklist:
<!--- Put an `x` in all the boxes that apply. -->
<!--- If your change requires a documentation PR, please link it appropriately -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly. (PR: #)
- [x] I have added tests to cover my changes (if not applicable, please state why)
- [x] All new and existing tests are passing.



Patch:
diff --git a/jest.config.js b/jest.config.js
index abda43df2..fe1563cc0 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -41,7 +41,8 @@ module.exports = {
   ],
 
   transformIgnorePatterns: [
-    'node_modules/(?!(@nuxt|nuxt))'
+    'node_modules/(?!(@nuxt|nuxt))',
+    'packages/utils/test/serialize\\.test\\.input\\.js'
   ],
 
   transform: {
diff --git a/packages/utils/src/serialize.js b/packages/utils/src/serialize.js
index f1b2ebdf0..4286c8224 100644
--- a/packages/utils/src/serialize.js
+++ b/packages/utils/src/serialize.js
@@ -44,7 +44,7 @@ export function serializeFunction (func) {
         return _
       }
     })
-    .replace(`${func.name || 'function'}(`, 'function (')
+    .replace(new RegExp(`${(func.name || 'function').replace('$', '\\$')}\\s*\\(`), 'function(')
     .replace('function function', 'function')
 }
 


Test patch:
diff --git a/packages/utils/test/serialize.test.input.js b/packages/utils/test/serialize.test.input.js
new file mode 100644
index 000000000..390680905
--- /dev/null
+++ b/packages/utils/test/serialize.test.input.js
@@ -0,0 +1,41 @@
+// NOTE: This file is excluded from being transformed by babel-jest and needs to use the CommonJS syntax.
+
+module.exports = {
+  arrow: {
+  // eslint-disable-next-line arrow-parens
+    fn1: foobar => {},
+    fn2: foobar => 1,
+    // eslint-disable-next-line arrow-parens
+    fn3: foobar => {
+      return 3
+    },
+    // eslint-disable-next-line arrow-parens
+    fn4: arg1 =>
+      2 * arg1,
+    fn5: () => {},
+    // eslint-disable-next-line arrow-parens
+    fn6: foobar => (foobar ? 1 : 0)
+  },
+  normal: {
+    fn: function () {} // eslint-disable-line object-shorthand
+  },
+  shorthand: {
+    fn () {},
+    // eslint-disable-next-line space-before-function-paren
+    $fn() {},
+    // eslint-disable-next-line no-unused-vars
+    fn_arrow () { const _ = rule => rule },
+    fn_script () {
+      return 'function xyz(){};a=false?true:xyz();'
+    },
+    fn_internal (arg) {
+      if (arg) {
+        return {
+          title () {
+            return 'test'
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/packages/utils/test/serialize.test.js b/packages/utils/test/serialize.test.js
index 2aa7d5693..2e8915f84 100644
--- a/packages/utils/test/serialize.test.js
+++ b/packages/utils/test/serialize.test.js
@@ -1,111 +1,69 @@
 import { serializeFunction, normalizeFunctions } from '../src/serialize'
+import data from './serialize.test.input'
+
+const RE_LINE_BREAKS = /[\r\n]+/g
 
 describe('util: serialize', () => {
   test('should normalize arrow functions', () => {
-    const obj = {
-      // eslint-disable-next-line arrow-parens
-      fn1: foobar => {},
-      fn2: foobar => 1,
-      // eslint-disable-next-line arrow-parens
-      fn3: foobar => {
-        return 3
-      },
-      // eslint-disable-next-line arrow-parens
-      fn4: arg1 =>
-        2 * arg1
-    }
-    expect(normalizeFunctions(obj).fn1.toString())
-      .toEqual('function anonymous(foobar\n) {\n\n}')
-    expect(normalizeFunctions(obj).fn2.toString())
-      .toEqual('function anonymous(foobar\n) {\nreturn 1\n}')
-    expect(normalizeFunctions(obj).fn3.toString())
-      .toEqual('function anonymous(foobar\n) {\nreturn 3;\n}')
-    expect(normalizeFunctions(obj).fn4.toString())
-      .toEqual('function anonymous(arg1\n) {\nreturn 2 * arg1\n}')
+    const normalized = normalizeFunctions(Object.assign({}, data.arrow))
+    expect(normalized.fn1.toString()).toEqual('function anonymous(foobar\n) {\n\n}')
+    expect(normalized.fn2.toString()).toEqual('function anonymous(foobar\n) {\nreturn 1\n}')
+    expect(normalized.fn3.toString()).toEqual('function anonymous(foobar\n) {\nreturn 3\n}')
+    expect(normalized.fn4.toString()).toEqual('function anonymous(arg1\n) {\nreturn 2 * arg1\n}')
   })
 
   test('should serialize normal function', () => {
-    const obj = {
-      fn: function () {} // eslint-disable-line object-shorthand
-    }
+    const obj = Object.assign({}, data.normal)
     expect(serializeFunction(obj.fn)).toEqual('function () {}')
   })
 
   test('should serialize shorthand function', () => {
-    const obj = {
-      fn () {}
-    }
+    const obj = Object.assign({}, data.shorthand)
     expect(serializeFunction(obj.fn)).toEqual('function() {}')
+    expect(serializeFunction(obj.$fn)).toEqual('function() {}')
+    expect(serializeFunction(obj.fn_arrow)).toEqual('function() { const _ = rule => rule }')
   })
 
   test('should serialize arrow function', () => {
-    const obj = {
-      fn: () => {}
-    }
-    expect(serializeFunction(obj.fn)).toEqual('() => {}')
+    const obj = Object.assign({}, data.arrow)
+    expect(serializeFunction(obj.fn5)).toEqual('() => {}')
   })
 
   test('should serialize arrow function with ternary in parens', () => {
-    const obj = {
-      // eslint-disable-next-line arrow-parens
-      fn: foobar => (foobar ? 1 : 0)
-    }
-    expect(serializeFunction(obj.fn)).toEqual('foobar => foobar ? 1 : 0')
+    const obj = Object.assign({}, data.arrow)
+    expect(serializeFunction(obj.fn6)).toEqual('foobar => (foobar ? 1 : 0)')
   })
 
   test('should serialize arrow function with single parameter', () => {
-    const obj = {
-      // eslint-disable-next-line arrow-parens
-      fn1: foobar => {},
-      fn2: foobar => 1,
-      // eslint-disable-next-line arrow-parens
-      fn3: foobar => {
-        return 3
-      },
-      // eslint-disable-next-line arrow-parens
-      fn4: arg1 =>
-        2 * arg1
-    }
+    const obj = Object.assign({}, data.arrow)
     expect(serializeFunction(obj.fn1)).toEqual('foobar => {}')
     expect(serializeFunction(obj.fn2)).toEqual('foobar => 1')
-    expect(serializeFunction(obj.fn3)).toEqual('foobar => {\n        return 3;\n      }')
-    expect(serializeFunction(obj.fn4)).toEqual('arg1 => 2 * arg1')
+    expect(serializeFunction(obj.fn3).replace(RE_LINE_BREAKS, '\n')).toEqual(`foobar => {
+      return 3
+    }`)
+    expect(serializeFunction(obj.fn4).replace(RE_LINE_BREAKS, '\n')).toEqual(`arg1 =>
+      2 * arg1`)
   })
 
   test('should not replace custom scripts', () => {
-    const obj = {
-      fn () {
-        return 'function xyz(){};a=false?true:xyz();'
-      }
-    }
+    const obj = Object.assign({}, data.shorthand)
 
-    expect(serializeFunction(obj.fn)).toEqual(`function () {
-        return 'function xyz(){};a=false?true:xyz();';
-      }`)
+    expect(serializeFunction(obj.fn_script).replace(RE_LINE_BREAKS, '\n')).toEqual(`function() {
+      return 'function xyz(){};a=false?true:xyz();'
+    }`)
   })
 
   test('should serialize internal function', () => {
-    const obj = {
-      fn (arg) {
-        if (arg) {
-          return {
-            title () {
-              return 'test'
-            }
+    const obj = Object.assign({}, data.shorthand)
+
+    expect(serializeFunction(obj.fn_internal).replace(RE_LINE_BREAKS, '\n')).toEqual(`function(arg) {
+      if (arg) {
+        return {
+          title: function () {
+            return 'test'
           }
         }
       }
-    }
-
-    expect(serializeFunction(obj.fn)).toEqual(`function(arg) {
-        if (arg) {
-          return {
-            title: function () {
-              return 'test';
-            }
-
-          };
-        }
-      }`)
+    }`)
   })
 })