perf(dart/transform) Restructure transform to independent phases
Update summary: - Removes the need for resolution, gaining transform speed at the cost of some precision and ability to detect errors - Generates type registrations in the package alongside their declarations - Ensures that line numbers do not change in transformed user code
This commit is contained in:
@ -0,0 +1,27 @@
|
||||
library bar;
|
||||
|
||||
import 'bar.dart';
|
||||
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||
|
||||
bool _visited = false;
|
||||
void setupReflection(reflector) {
|
||||
if (_visited) return;
|
||||
_visited = true;
|
||||
reflector
|
||||
..registerType(MyComponent, {
|
||||
"factory": () => new MyComponent(),
|
||||
"parameters": const [],
|
||||
"annotations": const [
|
||||
const Component(selector: 'soup', componentServices: const [ToolTip])
|
||||
]
|
||||
})
|
||||
..registerType(ToolTip, {
|
||||
"factory": () => new ToolTip(),
|
||||
"parameters": const [],
|
||||
"annotations": const [
|
||||
const Decorator(
|
||||
selector: '[tool-tip]', bind: const {'text': 'tool-tip'})
|
||||
]
|
||||
})
|
||||
..registerSetters({"text": (ToolTip o, String value) => o.text = value});
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
library angular2.src.transform.generated;
|
||||
|
||||
import 'package:angular2/src/reflection/reflection.dart' show reflector;
|
||||
import 'bar.dart' as i0;
|
||||
import 'package:angular2/src/core/annotations/annotations.dart' as i1;
|
||||
|
||||
setupReflection() {
|
||||
reflector
|
||||
..registerType(i0.MyComponent, {
|
||||
"factory": () => new i0.MyComponent(),
|
||||
"parameters": const [const []],
|
||||
"annotations": const [
|
||||
const i1.Component(
|
||||
selector: 'soup', componentServices: const [i0.ToolTip])
|
||||
]
|
||||
})
|
||||
..registerType(i0.ToolTip, {
|
||||
"factory": () => new i0.ToolTip(),
|
||||
"parameters": const [const []],
|
||||
"annotations": const [
|
||||
const i1.Decorator(
|
||||
selector: '[tool-tip]', bind: const {'text': 'tool-tip'})
|
||||
]
|
||||
})
|
||||
..registerSetters({"text": (i0.ToolTip o, String value) => o.text = value});
|
||||
}
|
10
modules/angular2/test/transform/chained_deps_files/bar.dart
Normal file
10
modules/angular2/test/transform/chained_deps_files/bar.dart
Normal file
@ -0,0 +1,10 @@
|
||||
library bar;
|
||||
|
||||
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||
import 'foo.dart' as dep;
|
||||
|
||||
@Component(
|
||||
selector: '[soup]', componentServices: const [dep.DependencyComponent])
|
||||
class MyComponent {
|
||||
MyComponent();
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
library bar;
|
||||
|
||||
import 'bar.dart';
|
||||
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||
import 'foo.dart' as dep;
|
||||
import 'foo.ngDeps.dart' as i0;
|
||||
|
||||
bool _visited = false;
|
||||
void setupReflection(reflector) {
|
||||
if (_visited) return;
|
||||
_visited = true;
|
||||
reflector
|
||||
..registerType(MyComponent, {
|
||||
"factory": () => new MyComponent(),
|
||||
"parameters": const [],
|
||||
"annotations": const [
|
||||
const Component(
|
||||
selector: '[soup]',
|
||||
componentServices: const [dep.DependencyComponent])
|
||||
]
|
||||
});
|
||||
i0.setupReflection(reflector);
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
library foo;
|
||||
|
||||
import 'foo.dart';
|
||||
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||
|
||||
bool _visited = false;
|
||||
void setupReflection(reflector) {
|
||||
if (_visited) return;
|
||||
_visited = true;
|
||||
reflector
|
||||
..registerType(DependencyComponent, {
|
||||
"factory": () => new DependencyComponent(),
|
||||
"parameters": const [],
|
||||
"annotations": const [const Component(selector: '[salad]')]
|
||||
});
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
library web_foo;
|
||||
|
||||
import 'package:angular2/src/core/application.dart';
|
||||
import 'package:angular2/src/reflection/reflection_capabilities.dart';
|
||||
import 'bar.dart';
|
||||
import 'a:web/bar.ngDeps.dart' as i0;
|
||||
|
||||
bool _visited = false;
|
||||
void setupReflection(reflector) {
|
||||
if (_visited) return;
|
||||
_visited = true;
|
||||
i0.setupReflection(reflector);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
library foo;
|
||||
|
||||
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||
|
||||
@Component(selector: '[salad]')
|
||||
class DependencyComponent {
|
||||
DependencyComponent();
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
library web_foo;
|
||||
|
||||
import 'package:angular2/src/core/application.dart';
|
||||
import 'package:angular2/src/reflection/reflection_capabilities.dart';
|
||||
import 'bar.dart';
|
||||
|
||||
void main() {
|
||||
reflector.reflectionCapabilities = new ReflectionCapabilities();
|
||||
bootstrap(MyComponent);
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
library bar;
|
||||
|
||||
import 'bar.dart';
|
||||
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||
import 'foo.dart';
|
||||
|
||||
bool _visited = false;
|
||||
void setupReflection(reflector) {
|
||||
if (_visited) return;
|
||||
_visited = true;
|
||||
reflector
|
||||
..registerType(MyComponent, {
|
||||
"factory": (MyContext c) => new MyComponent(c),
|
||||
"parameters": const [const [MyContext]],
|
||||
"annotations": const [
|
||||
const Component(componentServices: const [MyContext])
|
||||
]
|
||||
});
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
library angular2.src.transform.generated;
|
||||
|
||||
import 'package:angular2/src/reflection/reflection.dart' show reflector;
|
||||
import 'bar.dart' as i0;
|
||||
import 'foo.dart' as i1;
|
||||
import 'package:angular2/src/core/annotations/annotations.dart' as i2;
|
||||
|
||||
setupReflection() {
|
||||
reflector
|
||||
..registerType(i0.MyComponent, {
|
||||
"factory": (i1.MyContext c) => new i0.MyComponent(c),
|
||||
"parameters": const [const [i1.MyContext]],
|
||||
"annotations": const [
|
||||
const i2.Component(componentServices: const [i1.MyContext])
|
||||
]
|
||||
});
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
Tests that the reflection removal step:
|
||||
1. Comments out the import of reflection_capabilities.dart
|
||||
2. Comments out the instantiation of `ReflectionCapabilities`
|
||||
3. Adds the appropriate import.
|
||||
4. Adds the call to `setupReflection`
|
||||
5. Does not change line numbers in the source.
|
||||
6. Makes minimal changes to source offsets.
|
@ -0,0 +1,22 @@
|
||||
library angular2.test.transform.reflection_remover_files;
|
||||
|
||||
// This file is intentionally formatted as a string to avoid having the
|
||||
// automatic transformer prettify it.
|
||||
//
|
||||
// This file represents transformed user code. Because this code will be
|
||||
// linked to output by a source map, we cannot change line numbers from the
|
||||
// original code and we therefore add our generated code on the same line as
|
||||
// those we are removing.
|
||||
|
||||
var code = """
|
||||
library web_foo;
|
||||
|
||||
import 'package:angular2/src/core/application.dart';
|
||||
import 'package:angular2/src/reflection/reflection.dart';
|
||||
import 'package:angular2/src/reflection/reflection_capabilities.dart';import 'index.ngDeps.dart' as ngStaticInit;
|
||||
|
||||
void main() {
|
||||
reflector.reflectionCapabilities = new ReflectionCapabilities();ngStaticInit.setupReflection(reflector);
|
||||
bootstrap(MyComponent);
|
||||
}
|
||||
""";
|
@ -0,0 +1,10 @@
|
||||
library web_foo;
|
||||
|
||||
import 'package:angular2/src/core/application.dart';
|
||||
import 'package:angular2/src/reflection/reflection.dart';
|
||||
import 'package:angular2/src/reflection/reflection_capabilities.dart';
|
||||
|
||||
void main() {
|
||||
reflector.reflectionCapabilities = new ReflectionCapabilities();
|
||||
bootstrap(MyComponent);
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
library bar;
|
||||
|
||||
import 'bar.dart';
|
||||
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||
|
||||
bool _visited = false;
|
||||
void setupReflection(reflector) {
|
||||
if (_visited) return;
|
||||
_visited = true;
|
||||
reflector
|
||||
..registerType(MyComponent, {
|
||||
"factory": () => new MyComponent(),
|
||||
"parameters": const [],
|
||||
"annotations": const [const Component(selector: '[soup]')]
|
||||
});
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
library angular2.src.transform.generated;
|
||||
|
||||
import 'package:angular2/src/reflection/reflection.dart' show reflector;
|
||||
import 'bar.dart' as i0;
|
||||
import 'package:angular2/src/core/annotations/annotations.dart' as i1;
|
||||
|
||||
setupReflection() {
|
||||
reflector
|
||||
..registerType(i0.MyComponent, {
|
||||
"factory": () => new i0.MyComponent(),
|
||||
"parameters": const [const []],
|
||||
"annotations": const [const i1.Component(selector: '[soup]')]
|
||||
});
|
||||
}
|
@ -2,12 +2,12 @@ library web_foo;
|
||||
|
||||
import 'package:angular2/src/core/application.dart';
|
||||
import 'package:angular2/src/reflection/reflection.dart';
|
||||
import 'index.bootstrap.dart' as ngStaticInit;
|
||||
import 'index.ngDeps.dart' as ngStaticInit;
|
||||
import 'package:angular2/src/reflection/reflection_capabilities.dart';
|
||||
import 'bar.dart';
|
||||
|
||||
void main() {
|
||||
ngStaticInit.setupReflection();
|
||||
ngStaticInit.setupReflection(reflector);
|
||||
reflector.reflectionCapabilities = new ReflectionCapabilities();
|
||||
bootstrap(MyComponent);
|
||||
}
|
||||
|
@ -0,0 +1,15 @@
|
||||
library web_foo;
|
||||
|
||||
import 'index.dart';
|
||||
import 'package:angular2/src/core/application.dart';
|
||||
import 'package:angular2/src/reflection/reflection.dart';
|
||||
import 'package:angular2/src/reflection/reflection_capabilities.dart';
|
||||
import 'bar.dart';
|
||||
import 'bar.ngDeps.dart' as i0;
|
||||
|
||||
bool _visited = false;
|
||||
void setupReflection(reflector) {
|
||||
if (_visited) return;
|
||||
_visited = true;
|
||||
i0.setupReflection(reflector);
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
library bar;
|
||||
|
||||
import 'bar.dart';
|
||||
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||
|
||||
bool _visited = false;
|
||||
void setupReflection(reflector) {
|
||||
if (_visited) return;
|
||||
_visited = true;
|
||||
reflector
|
||||
..registerType(MyComponent, {
|
||||
"factory": () => new MyComponent(),
|
||||
"parameters": const [],
|
||||
"annotations": const [const Component(selector: '[soup]')]
|
||||
});
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
library angular2.src.transform.generated;
|
||||
|
||||
import 'package:angular2/src/reflection/reflection.dart' show reflector;
|
||||
import 'bar.dart' as i0;
|
||||
import 'package:angular2/src/core/annotations/annotations.dart' as i1;
|
||||
|
||||
setupReflection() {
|
||||
reflector
|
||||
..registerType(i0.MyComponent, {
|
||||
"factory": () => new i0.MyComponent(),
|
||||
"parameters": const [const []],
|
||||
"annotations": const [const i1.Component(selector: '[soup]')]
|
||||
});
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
library angular2.test;
|
||||
library angular2.test.transform;
|
||||
|
||||
import 'dart:io';
|
||||
import 'package:barback/barback.dart';
|
||||
@ -8,31 +8,30 @@ import 'package:dart_style/dart_style.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
import 'package:unittest/vm_config.dart';
|
||||
|
||||
import 'reflection_remover_files/expected/index.dart'
|
||||
as reflection_remover_output;
|
||||
|
||||
main() {
|
||||
useVMConfiguration();
|
||||
|
||||
// TODO(kegluneq): Add a test for generating multiple annotations.
|
||||
|
||||
group('Annotation tests:', _runTests);
|
||||
group('Integration tests:', _integrationTests);
|
||||
}
|
||||
|
||||
var formatter = new DartFormatter();
|
||||
var transform = new AngularTransformer(new TransformerOptions('web/index.dart',
|
||||
reflectionEntryPoint: 'web/index.dart',
|
||||
newEntryPoint: 'web/index.bootstrap.dart'));
|
||||
var transform = new AngularTransformerGroup(new TransformerOptions(
|
||||
'web/index.dart', reflectionEntryPoint: 'web/index.dart'));
|
||||
|
||||
class TestConfig {
|
||||
class IntegrationTestConfig {
|
||||
final String name;
|
||||
final Map<String, String> assetPathToInputPath;
|
||||
final Map<String, String> assetPathToExpectedOutputPath;
|
||||
|
||||
TestConfig(this.name,
|
||||
IntegrationTestConfig(this.name,
|
||||
{Map<String, String> inputs, Map<String, String> outputs})
|
||||
: this.assetPathToInputPath = inputs,
|
||||
this.assetPathToExpectedOutputPath = outputs;
|
||||
}
|
||||
|
||||
void _runTests() {
|
||||
void _integrationTests() {
|
||||
/*
|
||||
* Each test has its own directory for inputs & an `expected` directory for
|
||||
* expected outputs.
|
||||
@ -43,52 +42,53 @@ void _runTests() {
|
||||
var commonInputs = {
|
||||
'angular2|lib/src/core/annotations/annotations.dart':
|
||||
'../../lib/src/core/annotations/annotations.dart',
|
||||
'angular2|lib/src/core/application.dart': 'common.dart',
|
||||
'angular2|lib/src/core/application.dart': 'common/application.dart',
|
||||
'angular2|lib/src/reflection/reflection_capabilities.dart':
|
||||
'reflection_capabilities.dart'
|
||||
'common/reflection_capabilities.dart'
|
||||
};
|
||||
|
||||
var tests = [
|
||||
new TestConfig('Simple',
|
||||
new IntegrationTestConfig('Simple',
|
||||
inputs: {
|
||||
'a|web/index.dart': 'simple_annotation_files/index.dart',
|
||||
'a|web/bar.dart': 'simple_annotation_files/bar.dart'
|
||||
},
|
||||
outputs: {
|
||||
'a|web/index.bootstrap.dart':
|
||||
'simple_annotation_files/expected/index.bootstrap.dart',
|
||||
'a|web/index.dart': 'simple_annotation_files/expected/index.dart',
|
||||
'a|web/bar.ngDeps.dart':
|
||||
'simple_annotation_files/expected/bar.ngDeps.dart',
|
||||
'a|web/index.ngDeps.dart':
|
||||
'simple_annotation_files/expected/index.ngDeps.dart'
|
||||
}),
|
||||
new TestConfig('Two injected dependencies',
|
||||
new IntegrationTestConfig('Reflection Remover',
|
||||
inputs: {'a|web/index.dart': 'reflection_remover_files/index.dart'},
|
||||
outputs: {'a|web/index.dart': reflection_remover_output.code}),
|
||||
new IntegrationTestConfig('Two injected dependencies',
|
||||
inputs: {
|
||||
'a|web/index.dart': 'two_deps_files/index.dart',
|
||||
'a|web/foo.dart': 'two_deps_files/foo.dart',
|
||||
'a|web/bar.dart': 'two_deps_files/bar.dart'
|
||||
},
|
||||
outputs: {
|
||||
'a|web/index.bootstrap.dart':
|
||||
'two_deps_files/expected/index.bootstrap.dart'
|
||||
'a|web/bar.ngDeps.dart': 'two_deps_files/expected/bar.ngDeps.dart'
|
||||
}),
|
||||
new TestConfig('List of types',
|
||||
new IntegrationTestConfig('List of types',
|
||||
inputs: {
|
||||
'a|web/index.dart': 'list_of_types_files/index.dart',
|
||||
'a|web/foo.dart': 'list_of_types_files/foo.dart',
|
||||
'a|web/bar.dart': 'list_of_types_files/bar.dart'
|
||||
},
|
||||
outputs: {
|
||||
'a|web/index.bootstrap.dart':
|
||||
'list_of_types_files/expected/index.bootstrap.dart'
|
||||
'a|web/bar.ngDeps.dart': 'list_of_types_files/expected/bar.ngDeps.dart'
|
||||
}),
|
||||
new TestConfig('Component with synthetic Constructor',
|
||||
new IntegrationTestConfig('Component with synthetic Constructor',
|
||||
inputs: {
|
||||
'a|web/index.dart': 'synthetic_ctor_files/index.dart',
|
||||
'a|web/bar.dart': 'synthetic_ctor_files/bar.dart'
|
||||
},
|
||||
outputs: {
|
||||
'a|web/index.bootstrap.dart':
|
||||
'synthetic_ctor_files/expected/index.bootstrap.dart'
|
||||
'a|web/bar.ngDeps.dart': 'synthetic_ctor_files/expected/bar.ngDeps.dart'
|
||||
}),
|
||||
new TestConfig('Component with two annotations',
|
||||
new IntegrationTestConfig('Component with two annotations',
|
||||
inputs: {
|
||||
'a|web/index.dart': 'two_annotations_files/index.dart',
|
||||
'a|web/bar.dart': 'two_annotations_files/bar.dart',
|
||||
@ -96,17 +96,25 @@ void _runTests() {
|
||||
'../../lib/src/core/annotations/template.dart'
|
||||
},
|
||||
outputs: {
|
||||
'a|web/index.bootstrap.dart':
|
||||
'two_annotations_files/expected/index.bootstrap.dart'
|
||||
'a|web/bar.ngDeps.dart': 'two_annotations_files/expected/bar.ngDeps.dart'
|
||||
}),
|
||||
new TestConfig('Basic `bind`',
|
||||
new IntegrationTestConfig('Basic `bind`',
|
||||
inputs: {
|
||||
'a|web/index.dart': 'basic_bind_files/index.dart',
|
||||
'a|web/bar.dart': 'basic_bind_files/bar.dart'
|
||||
},
|
||||
outputs: {
|
||||
'a|web/index.bootstrap.dart':
|
||||
'basic_bind_files/expected/index.bootstrap.dart'
|
||||
'a|web/bar.ngDeps.dart': 'basic_bind_files/expected/bar.ngDeps.dart'
|
||||
}),
|
||||
new IntegrationTestConfig('Chained dependencies',
|
||||
inputs: {
|
||||
'a|web/index.dart': 'chained_deps_files/index.dart',
|
||||
'a|web/foo.dart': 'chained_deps_files/foo.dart',
|
||||
'a|web/bar.dart': 'chained_deps_files/bar.dart'
|
||||
},
|
||||
outputs: {
|
||||
'a|web/bar.ngDeps.dart': 'chained_deps_files/expected/bar.ngDeps.dart',
|
||||
'a|web/foo.ngDeps.dart': 'chained_deps_files/expected/foo.ngDeps.dart'
|
||||
})
|
||||
];
|
||||
|
||||
@ -118,8 +126,8 @@ void _runTests() {
|
||||
config.assetPathToInputPath
|
||||
..addAll(commonInputs)
|
||||
..forEach((key, value) {
|
||||
config.assetPathToInputPath[
|
||||
key] = cache.putIfAbsent(value, () => _readFile(value));
|
||||
config.assetPathToInputPath[key] =
|
||||
cache.putIfAbsent(value, () => _readFile(value));
|
||||
});
|
||||
config.assetPathToExpectedOutputPath.forEach((key, value) {
|
||||
config.assetPathToExpectedOutputPath[key] = cache.putIfAbsent(value, () {
|
||||
@ -133,7 +141,7 @@ void _runTests() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Smoothes over differences in CWD between IDEs and running tests in Travis.
|
||||
/// Smooths over differences in CWD between IDEs and running tests in Travis.
|
||||
String _readFile(String path) {
|
||||
for (var myPath in [path, 'test/transform/${path}']) {
|
||||
var file = new File(myPath);
|
||||
@ -141,5 +149,5 @@ String _readFile(String path) {
|
||||
return file.readAsStringSync();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return path;
|
||||
}
|
||||
|
@ -0,0 +1,20 @@
|
||||
library bar;
|
||||
|
||||
import 'bar.dart';
|
||||
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||
import 'package:angular2/src/core/annotations/template.dart';
|
||||
|
||||
bool _visited = false;
|
||||
void setupReflection(reflector) {
|
||||
if (_visited) return;
|
||||
_visited = true;
|
||||
reflector
|
||||
..registerType(MyComponent, {
|
||||
"factory": () => new MyComponent(),
|
||||
"parameters": const [],
|
||||
"annotations": const [
|
||||
const Component(selector: '[soup]'),
|
||||
const Template(inline: 'Salad')
|
||||
]
|
||||
});
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
library angular2.src.transform.generated;
|
||||
|
||||
import 'package:angular2/src/reflection/reflection.dart' show reflector;
|
||||
import 'bar.dart' as i0;
|
||||
import 'package:angular2/src/core/annotations/annotations.dart' as i1;
|
||||
import 'package:angular2/src/core/annotations/template.dart' as i2;
|
||||
|
||||
setupReflection() {
|
||||
reflector
|
||||
..registerType(i0.MyComponent, {
|
||||
"factory": () => new i0.MyComponent(),
|
||||
"parameters": const [const []],
|
||||
"annotations": const [
|
||||
const i1.Component(selector: '[soup]'),
|
||||
const i2.Template(inline: 'Salad')
|
||||
]
|
||||
});
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
library bar;
|
||||
|
||||
import 'bar.dart';
|
||||
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||
import 'foo.dart' as prefix;
|
||||
|
||||
bool _visited = false;
|
||||
void setupReflection(reflector) {
|
||||
if (_visited) return;
|
||||
_visited = true;
|
||||
reflector
|
||||
..registerType(MyComponent, {
|
||||
"factory":
|
||||
(prefix.MyContext c, String inValue) => new MyComponent(c, inValue),
|
||||
"parameters": const [const [prefix.MyContext], const [String]],
|
||||
"annotations": const [
|
||||
const Component(selector: prefix.preDefinedSelector)
|
||||
]
|
||||
});
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
library angular2.src.transform.generated;
|
||||
|
||||
import 'package:angular2/src/reflection/reflection.dart' show reflector;
|
||||
import 'bar.dart' as i0;
|
||||
import 'foo.dart' as i1;
|
||||
import 'package:angular2/src/core/annotations/annotations.dart' as i2;
|
||||
|
||||
setupReflection() {
|
||||
reflector
|
||||
..registerType(i0.MyComponent, {
|
||||
"factory":
|
||||
(i1.MyContext c, String inValue) => new i0.MyComponent(c, inValue),
|
||||
"parameters": const [const [i1.MyContext, String]],
|
||||
"annotations": const [const i2.Component(selector: i1.preDefinedSelector)]
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user